Slider
A slider picks a number, or a range of two, by dragging or with the keyboard.
import { Slider } from "@/components/ui/slider";
export default function SliderDemo() {
return (
<div className="flex w-full max-w-xs flex-col gap-8">
<Slider defaultValue={40} label="Volume" />
<Slider defaultValue={[20, 70]} label="Price" />
<Slider defaultValue={50} label="Brightness" disabled />
</div>
);
}The code
The component is a single file, read from the repository when the site is built. Press Show code to see all of it.
import type { ComponentProps } from "react";
import { Slider as BaseSlider } from "@base-ui/react/slider";
import { cx } from "@/lib/cx";
// The focused element is the hidden range input inside the thumb, not the thumb itself, so the ring
// is drawn when the thumb *contains* keyboard focus rather than with focus-visible on the thumb.
const thumbRing =
"outline-hidden has-[:focus-visible]:outline-2 has-[:focus-visible]:outline-solid has-[:focus-visible]:outline-offset-2 has-[:focus-visible]:outline-text";
// Pass a number for one thumb or an array of two for a range. Every thumb needs a name for
// assistive tech, so `label` is required; a range gets "minimum" and "maximum" added to it.
export function Slider({
label,
className,
...props
}: Omit<ComponentProps<typeof BaseSlider.Root>, "className" | "children"> & {
label: string;
className?: string;
}) {
const current = props.value ?? props.defaultValue ?? 0;
const thumbs = Array.isArray(current) ? current.length : 1;
return (
<BaseSlider.Root className={cx("w-full", className)} {...props}>
<BaseSlider.Control className="flex h-5 w-full touch-none items-center select-none">
<BaseSlider.Track className="relative h-1.5 w-full rounded-full bg-border-strong select-none">
<BaseSlider.Indicator className="rounded-full bg-accent data-[disabled]:bg-text-secondary" />
{Array.from({ length: thumbs }, (_, index) => (
<BaseSlider.Thumb
key={index}
index={thumbs > 1 ? index : undefined}
aria-label={thumbs > 1 ? `${label} ${index === 0 ? "minimum" : "maximum"}` : label}
className={cx(
"block size-5 cursor-grab rounded-full bg-text transition-transform duration-150",
thumbRing,
"data-[dragging]:scale-110 data-[dragging]:cursor-grabbing",
"data-[disabled]:cursor-not-allowed data-[disabled]:bg-text-secondary",
)}
/>
))}
</BaseSlider.Track>
</BaseSlider.Control>
</BaseSlider.Root>
);
}Props
| Prop | Type | Default | What it does |
|---|---|---|---|
label | string | — | Required. The name of what the slider controls. A range adds "minimum" and "maximum" to it. |
defaultValue | number | number[] | — | The starting value. An array of two makes a range with two thumbs. |
value | number | number[] | — | The value, when you control it yourself. |
min, max, step | number | 0, 100, 1 | The smallest and largest value, and the size of one step. |
disabled | boolean | false | Turns the slider off. |
className | string | — | Extra classes, added after the slider's own. |
Every other prop, such as onValueChange, goes to the Base UI Slider underneath.
The thinking
A slider is for a value where roughly right is good enough, like volume. For an exact number, an input is better.
- Built on Base UI: It handles dragging, clicking the track, keyboard steps and the value itself. This file draws the track and thumb.
- A range is an array: Pass
[20, 70]and you get two thumbs and a filled stretch between them. A single number gives one thumb. - Every thumb has a name: The label is required, so a slider can't be shipped without one.
Interface design
- Track: 6px tall and fully rounded, in the strong border color. The filled part is the accent, 7.7:1 against the track.
- Thumb: A 20px circle in the main text color.
- Disabled: The filled part and the thumb both turn grey.
Interaction
- Pointer: Drag the thumb, or click the track to jump there. The thumb grows slightly while it is being dragged, and the cursor becomes a closed hand.
- Keyboard: The arrow keys move one step, Home and End jump to the ends.
- Focus: The focused thumb draws a 2px outline, 2px away.
- Disabled: It can't be dragged or focused, and shows a not-allowed cursor.
Empathy
- Announced: Each thumb is read as a slider with its name, its value and its limits.
- No printed value: This slider doesn't show a number. If the exact value matters, show it in text next to the slider.
- Target size: The thumb is 20px, under the 24px minimum of WCAG 2.2 AA. The whole track also responds to a click, and the arrow keys give a precise alternative to dragging.