Fiber UI LogoFiberUI

Slider

An input for selecting a value within a range.

Basic Usage

import { Slider } from "@repo/ui/components/slider";

/* BASIC USAGE EXAMPLE */
export const Example1 = () => {
    return <Slider defaultValue={[50]} />;
};

Controlled

Value: 30

"use client";

import { useState } from "react";
import { Slider } from "@repo/ui/components/slider";

/* CONTROLLED SLIDER EXAMPLE */
export const Example2 = () => {
    const [value, setValue] = useState([30]);

    return (
        <div className="w-full max-w-xs space-y-4">
            <Slider value={value} onChange={(v) => setValue(v as number[])} />
            <p className="text-muted-foreground text-center text-sm">
                Value:{" "}
                <span className="text-foreground font-medium">{value[0]}</span>
            </p>
        </div>
    );
};

Range Slider

Range: 2575

"use client";

import { useState } from "react";
import { Slider } from "@repo/ui/components/slider";

/* RANGE SLIDER EXAMPLE */
export const Example3 = () => {
    const [value, setValue] = useState([25, 75]);

    return (
        <div className="w-full max-w-xs space-y-4">
            <Slider value={value} onChange={(v) => setValue(v as number[])} />
            <p className="text-muted-foreground text-center text-sm">
                Range:{" "}
                <span className="text-foreground font-medium">{value[0]}</span>
                {""}
                <span className="text-foreground font-medium">{value[1]}</span>
            </p>
        </div>
    );
};

Disabled

import { Slider } from "@repo/ui/components/slider";

/* DISABLED SLIDER EXAMPLE */
export const Example4 = () => {
    return (
        <div className="w-full max-w-xs space-y-4">
            <Slider defaultValue={[50]} isDisabled />
        </div>
    );
};

With Labels

75%
50%
"use client";

import { useState } from "react";
import { Slider } from "@repo/ui/components/slider";
import { Label } from "@repo/ui/components/label";

/* SLIDER WITH STEP AND CUSTOM RANGE */
export const Example5 = () => {
    const [volume, setVolume] = useState([75]);
    const [brightness, setBrightness] = useState([50]);

    return (
        <div className="w-full max-w-xs space-y-6">
            <div className="space-y-3">
                <div className="flex items-center justify-between">
                    <Label>Volume</Label>
                    <span className="text-muted-foreground text-sm">
                        {volume[0]}%
                    </span>
                </div>
                <Slider
                    value={volume}
                    onChange={(v) => setVolume(v as number[])}
                    minValue={0}
                    maxValue={100}
                    step={5}
                />
            </div>
            <div className="space-y-3">
                <div className="flex items-center justify-between">
                    <Label>Brightness</Label>
                    <span className="text-muted-foreground text-sm">
                        {brightness[0]}%
                    </span>
                </div>
                <Slider
                    value={brightness}
                    onChange={(v) => setBrightness(v as number[])}
                    minValue={0}
                    maxValue={100}
                    step={10}
                />
            </div>
        </div>
    );
};

Component Code

"use client";
import {
    Slider as AriaSlider,
    SliderProps as AriaSliderProps,
    SliderThumb,
    SliderTrack,
} from "react-aria-components";
import { cn } from "tailwind-variants";

export interface SliderProps<T> extends AriaSliderProps<T> {
    label?: string;
    thumbLabels?: string[];
}

export function Slider<T extends number | number[]>({
    thumbLabels,
    className,
    ...props
}: SliderProps<T>) {
    return (
        <AriaSlider
            data-slot="slider"
            {...props}
            className={cn(
                "relative flex w-full touch-none select-none items-center",
                className,
            )}
        >
            {/* SliderTrack is a tall container for the thumb to move in */}
            <SliderTrack
                data-slot="slider-track"
                className="relative h-5 w-full"
            >
                {({ state }) => (
                    <>
                        {/* Visible track bar - centered vertically */}
                        <div className="bg-muted absolute top-1/2 h-1.5 w-full -translate-y-1/2 rounded-full" />

                        {/* Fill bar - centered vertically */}
                        {state.values.length === 1 ? (
                            // Single thumb - fill from start to thumb
                            <div
                                className="bg-primary absolute top-1/2 h-1.5 -translate-y-1/2 rounded-full"
                                style={{
                                    width: `${state.getThumbPercent(0) * 100}%`,
                                }}
                            />
                        ) : state.values.length === 2 ? (
                            // Range slider - fill between the two thumbs
                            <div
                                className="bg-primary absolute top-1/2 h-1.5 -translate-y-1/2 rounded-full"
                                style={{
                                    left: `${state.getThumbPercent(0) * 100}%`,
                                    width: `${(state.getThumbPercent(1) - state.getThumbPercent(0)) * 100}%`,
                                }}
                            />
                        ) : null}

                        {/* Thumbs - React Aria sets left position, we need to center vertically */}
                        {state.values.map((_, i) => (
                            <SliderThumb
                                key={i}
                                index={i}
                                aria-label={thumbLabels?.[i]}
                                className="border-primary bg-background ring-ring/50 absolute top-1/2 block size-4 rounded-full border shadow transition-colors hover:ring-4 focus-visible:outline-none focus-visible:ring-4 disabled:pointer-events-none disabled:opacity-50"
                            />
                        ))}
                    </>
                )}
            </SliderTrack>
        </AriaSlider>
    );
}