ComponentsForm
Slider
Chooses a number, or a range between two numbers, by dragging a handle along a track.
Import
import { Slider } from "@booleanpress/ui/slider"Usage
A slider suits a value where the rough position matters more than the exact figure, such as a rate or a threshold. When people need an exact number, add an input beside it, as in With input.
import { Label } from "@booleanpress/ui/label"
import { Slider } from "@booleanpress/ui/slider"
export function SendingRate() {
return (
<>
<Label id="rate">Sending rate</Label>
<Slider defaultValue={[50]} aria-labelledby="rate" />
</>
)
}The value is always an array: one number for one handle, two for a range. It is uncontrolled with defaultValue, or controlled with value and onValueChange; onValueCommit fires once when a drag or a key press ends. min, max (0 and 100 by default) and step bound it, and minStepsBetweenThumbs keeps a range's handles apart.
Name it with aria-labelledby (a visible label's id) or aria-label: the slider passes the name to its handles, the parts with the slider role. A range's handles add their own names, "Minimum" and "Maximum" (the provider strings sliderMinimum and sliderMaximum; three or more handles use sliderValue). size is sm, default or lg, and follows the provider's controlSize. Show the value next to it yourself, formatted with Intl and the provider's locale (useUiLocale), as in Controlled. With name, it submits its value in a form.
Examples
Basic
One handle, from 0 to 100.
Step
step={20} moves the value in steps of 20.
Range
Two values make two handles, named Minimum and Maximum.
Vertical
orientation="vertical", each named by the caption under it.
import { Slider } from "@booleanpress/ui/slider"
const QUEUES = [
{ id: "queue-transactional", label: "Orders", value: 70 },
{ id: "queue-marketing", label: "News", value: 20 },
{ id: "queue-alerts", label: "Alerts", value: 40 },
]
export default function SliderVertical() {
return (
<div className="flex gap-10">
{QUEUES.map((queue) => (
<div key={queue.id} className="flex flex-col items-center gap-3">
<Slider
orientation="vertical"
defaultValue={[queue.value]}
aria-labelledby={queue.id}
className="h-28"
/>
<span id={queue.id} className="text-xs/normal text-muted-foreground">
{queue.label}
</span>
</div>
))}
</div>
)
}Controlled
value and onValueChange, with the value shown beside the label in the reader's number format.
import { useState } from "react"
import { useUiLocale } from "@booleanpress/ui/provider"
import { Label } from "@booleanpress/ui/label"
import { Slider } from "@booleanpress/ui/slider"
export default function SliderControlled() {
const [limit, setLimit] = useState([2500])
const { locale } = useUiLocale()
const formatted = new Intl.NumberFormat(locale).format(limit[0])
return (
<div className="flex w-64 flex-col gap-3">
<div className="flex items-center justify-between gap-2">
<Label id="daily-limit">Daily sending limit</Label>
<span className="text-sm/normal text-muted-foreground tabular-nums">{formatted} emails</span>
</div>
<Slider value={limit} onValueChange={setLimit} min={0} max={10000} step={500} aria-labelledby="daily-limit" />
</div>
)
}With input
A number field and the slider share one value, so either can change it.
import { useState } from "react"
import { Input } from "@booleanpress/ui/input"
import { Label } from "@booleanpress/ui/label"
import { Slider } from "@booleanpress/ui/slider"
export default function SliderWithInput() {
const [value, setValue] = useState(50)
return (
<div className="flex w-64 flex-col gap-3">
<Label htmlFor="batch-size">Batch size</Label>
<Input
id="batch-size"
type="number"
min={0}
max={100}
value={value}
onChange={(event) => setValue(Math.min(100, Math.max(0, Number(event.target.value))))}
/>
<Slider value={[value]} onValueChange={([next]) => setValue(next)} aria-label="Batch size" />
</div>
)
}Sizes
size="sm", the default and size="lg": the handle and the track scale together.
import { Slider } from "@booleanpress/ui/slider"
export default function SliderSizes() {
return (
<div className="flex w-64 flex-col gap-8">
<Slider size="sm" defaultValue={[30]} aria-label="Small" />
<Slider defaultValue={[50]} aria-label="Default" />
<Slider size="lg" defaultValue={[70]} aria-label="Large" />
</div>
)
}Disabled
disabled dims the slider and removes its handles from the tab order.
import { Label } from "@booleanpress/ui/label"
import { Slider } from "@booleanpress/ui/slider"
export default function SliderDisabled() {
return (
<div className="flex w-64 flex-col gap-8">
<div className="flex flex-col gap-3">
<Label id="disabled-rate">Sending rate</Label>
<Slider defaultValue={[50]} disabled aria-labelledby="disabled-rate" />
</div>
<div className="flex flex-col gap-3">
<Label id="disabled-window">Quiet hours</Label>
<Slider defaultValue={[20, 80]} disabled aria-labelledby="disabled-window" />
</div>
</div>
)
}Accessibility
- Semantics
- Each handle is
role="slider"witharia-valuenow,aria-valuemin,aria-valuemaxandaria-orientation. The track and range are decoration. Withname, each value is also a hidden input for forms. - Labels
- Pass
aria-labelledbyoraria-labelto the slider; it names every handle. A range's handles are named with the slider's name and "Minimum" or "Maximum" from the provider strings.aria-describedbyreaches the handles too, for a hint or an error. - Focus
- Each handle is a tab stop; a disabled slider has none. Focus is a 1px
--ringoutline 2px outside the handle. - Known limits
- The value is announced as a bare number. Radix does not take a text form of the value (
aria-valuetext), so show the unit next to the slider. - The handles cannot be disabled one at a time;
disabledcovers the whole slider. - A slider is hard to set to an exact value by pointer; pair it with an input when the exact number matters.
- The value is announced as a bare number. Radix does not take a text form of the value (
Keyboard
| Key | Behaviour |
|---|---|
| โorโ | Increases the value by one step (โ decreases it in right-to-left pages). |
| โorโ | Decreases the value by one step (โ increases it in right-to-left pages). |
| Shiftโ | With any arrow key, moves ten steps that way. |
| Page UporPage Down | Increases or decreases the value by ten steps. |
| HomeorEnd | Sets the minimum or the maximum. |
| Tab | Moves to the next handle, then out of the slider. |
API
Slider
Renders Radix Slider.Root and passes it every other prop.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | boolean | ||
defaultValue | number[] | The values at the start, one per handle, when it controls itself. [min] by default. | |
dir | "ltr" | "rtl" | The reading direction. The provider's dir by default. | |
disabled | boolean | Dims the slider and ignores the pointer and the keyboard. | |
form | string | ||
inverted | boolean | Whether the value runs the other way along the track. | |
max | number | 100 | The highest value. 100 by default. |
min | number | 0 | The lowest value. 0 by default. |
minStepsBetweenThumbs | number | The fewest steps a range's handles keep between them. 0 by default. | |
name | string | The form field name; the value is submitted with the form. | |
onValueChange | ((value: number[]) => void) | Called with the new values while the handle moves. | |
onValueCommit | ((value: number[]) => void) | Called with the values once, when a drag or a key press ends. | |
orientation | "horizontal" | "vertical" | horizontal (default) or vertical. | |
size | "default" | "sm" | "lg" | The track and handle: sm a 16px handle, default 20px, lg 24px. The provider's controlSize by default. | |
step | number | The amount each move changes the value by. 1 by default. | |
value | number[] | The values, one per handle, when you control them. Pair it with onValueChange. |
Every part takes className, merged with its defaults by cn(), and ref, which reaches the element it renders.
Data attributes: data-slot="slider" (Slider), and data-size.
Provider strings: sliderMinimum, sliderMaximum, sliderValue (BooleanUIProvider's strings).
Theming
The track is --border, the filled range --primary. The handle is a --border ring round a knob in --background (--card on hover); focus is --ring. A disabled slider is drawn at 60% opacity.
The tokens its classes read. Change their values in your theme, and it follows.
| Token | Used for |
|---|---|
--background | background |
--border | background |
--card | background |
--primary | background |
--ring | outline |