Slider
A fully accessible slider component for selecting values within a range. Supports single or multiple thumbs, custom formatting, and keyboard interaction.
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: 25 – 75
"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>
);
}
Skeleton
An animated skeleton loader for simulating content layout while data is fetching. Improves perceived performance by reducing layout shift and visual jarring.
Switch
An accessible toggle switch for binary states. Supports custom labels, sizes, and colors, with smooth animations and full keyboard accessibility.