Slider
A single coarse, non-clinical preference set by dragging a thumb along a track, and never a clinical reading or a precise measurement.
Preview
slider · base base · style base-lyraOpen under the product themeInstallation
pnpm dlx shadcn@latest add @opsinjs/slider
The @opsinjs namespace is declared in your components.json. Everything it installs is code you then own. There is no runtime package to keep in step.
Usage
import { Slider } from "@/components/ui/slider"<Slider
label="Screen brightness"
value={brightness}
onValueChange={setBrightness}
/>When to use it
Use it when
- A coarse, non-clinical preference such as a brightness level, where the exact number does not matter.
- A value adjusted by feel, where sliding towards more or less beats typing a figure.
- A setting with a small set of stops the reader can actually mean.
Do not use it when
- You are capturing a clinical reading. A slider turns a value the person meant into a value the pixel chose.Use
reading-inputinstead. - You need a precise non-clinical quantity where the digits matter.Use
number-fieldinstead. - The reader picks one of a small set of named options rather than a point on a continuum.Use
segmented-controlinstead.
Anatomy
SliderThe root. Holds value, min, max and step, and sits on neither colour axis.Controlled byvalueSlider.LabelThe visible label, wired to the thumb's hidden input so it names the control.Controlled bylabelSlider.ValueA text readout mirroring the value, quieter than the label, with tabular figures.Slider.ControlThe pointer region. Floors its height at the target minimum and blocks touch scrolling.Slider.TrackThe thin muted rail with a hairline.Slider.IndicatorThe filled portion, from the track start to the thumb, in the bridged primary role.Slider.ThumbThe draggable knob. A 44px hit area nesting a real range input.Controlled bydisabled
- Slider 1
- Slider.Label 1
- Slider.Value 1
- Slider.Control 1
- Slider.Track 1
- Slider.Indicator 1
- Slider.Thumb 1
- Slider.Track 1
Examples
A coarse preference
A fictional notification volume from nought to a hundred, driven by value and
onValueChange.
slider-a-coarse-preference · base base · style base-lyraOpen under the product themeWith steps
min, max and step snap the thumb to stops: a fictional map zoom running
from one to nine in twos.
slider-with-steps · base base · style base-lyraOpen under the product themeContent guidelines
Name the preference, not the gesture: Screen brightness, never Drag to set brightness. The label is the accessible name and is heard before the value. Keep units out of the readout, because a unit invites the reader to treat a rough position as a measurement.
Do
"Screen brightness" with a bare nought to a hundred readout names the thing adjusted.
Don’t
"Blood pressure" with a mmHg readout dresses a rough position as a measurement.
Accessibility
This component was audited against WCAG 2.2 AA in a source pass and a rendered pass. The audit is author-run, it is not an independent review, and clinical review is still pending. The rendered pass was clean: no axe violations, no target-size breach and no reflow failure. The source already handled the flagged risks, so nothing changed here.
- 2.5.7 Dragging: the thumb wraps Base UI unmodified, so click-to-position plus Arrow, Home, End and Page keys give every drag a keyboard alternative.
- 4.1.2 Name, Role, Value: role,
aria-valuenow,minandmaxcome from the nested real range input, and theSlider.Labeltext is forwarded onto it so the name is present at render. - 2.5.8: the thumb hit area is floored at
--opsin-target-minimumon both axes, with the knob drawn smaller inside.
Two residual items. The knob's colour fade on interaction has no reduced-motion
guard, but it is a colour change and not positional motion, so any neutralisation
belongs in a shared stylesheet. The Slider.Value readout renders as an output
with a polite live region, so while dragging the value is spoken twice, a
verbosity and not a defect. Contrast pairs remain unmeasured.
| Key | Action | Notes |
|---|---|---|
| Tab | Moves focus onto the thumb | One tab stop, on the thumb's nested range input. |
| Shift+Tab | Moves focus back out of the control | A single stop in reverse too. |
| Arrow Right, Arrow Up | Increases the value by one step | The `step` prop. A right-to-left layout swaps Left and Right. |
| Arrow Left, Arrow Down | Decreases the value by one step | The `step` prop, and the value stops at `min`. |
| Home | Sets the value to the minimum | Jumps the thumb to the start. |
| End | Sets the value to the maximum | Jumps the thumb to the end. |
| Page Up, Page Down | Increases or decreases by a larger step | Base UI's default of ten, not the `step` prop. |
| Pair | Theme | APCA Lc | WCAG 2.2 | Floor |
|---|---|---|---|---|
| body text on the page | light | 101.6 | 17.18:1 | Pass |
| body text on the page | dark | -100.5 | 18.00:1 | Pass |
| body text on a card | light | 104.7 | 17.96:1 | Pass |
| body text on a card | dark | -99.6 | 16.32:1 | Pass |
| body text on the muted ground | light | 98.1 | 16.32:1 | Pass |
| body text on the muted ground | dark | -97.5 | 13.77:1 | Pass |
| secondary text on the page | light | 83.9 | 7.42:1 | Pass |
| secondary text on the page | dark | -80.7 | 13.44:1 | Pass |
| secondary text on a card | light | 87.0 | 7.76:1 | Pass |
| secondary text on a card | dark | -79.8 | 12.19:1 | Pass |
| secondary text on the muted ground | light | 80.4 | 7.05:1 | Pass |
| secondary text on the muted ground | dark | -77.8 | 10.28:1 | Pass |
| a hairline boundary on the page | light | 19.3 | 1.41:1 | Below floor |
| a hairline boundary on the page | dark | -8.2 | 1.90:1 | Below floor |
| a hairline boundary on a card | light | 22.4 | 1.47:1 | Below floor |
| a hairline boundary on a card | dark | -7.3 | 1.72:1 | Below floor |
| a hairline boundary on the muted ground | light | 15.8 | 1.34:1 | Below floor |
| a hairline boundary on the muted ground | dark | 0.0 | 1.45:1 | Below floor |
| a placeholder boundary on the page | light | 69.8 | 4.52:1 | Pass |
| a placeholder boundary on the page | dark | -51.0 | 7.62:1 | Pass |
| a placeholder boundary on a card | light | 72.8 | 4.72:1 | Pass |
| a placeholder boundary on a card | dark | -50.1 | 6.91:1 | Pass |
| a placeholder boundary on the muted ground | light | 66.2 | 4.29:1 | Pass |
| a placeholder boundary on the muted ground | dark | -48.1 | 5.83:1 | Pass |
| a placeholder fill on the page | light | 8.3 | 1.18:1 | Below floor |
| a placeholder fill on the page | dark | -8.2 | 1.90:1 | Below floor |
| a placeholder fill on a card | light | 11.4 | 1.23:1 | Below floor |
| a placeholder fill on a card | dark | -7.3 | 1.72:1 | Below floor |
| a placeholder fill on the muted ground | light | 0.0 | 1.12:1 | Below floor |
| a placeholder fill on the muted ground | dark | 0.0 | 1.45:1 | Below floor |
| a placeholder boundary on its own fill | light | 58.9 | 3.84:1 | Pass |
| a placeholder boundary on its own fill | dark | -41.5 | 4.01:1 | Below floor |
| the card hairline on the page | light | 69.8 | 4.52:1 | Pass |
| the card hairline on the page | dark | -28.7 | 4.19:1 | Below floor |
| the card hairline on a card | light | 72.8 | 4.72:1 | Pass |
| the card hairline on a card | dark | -27.9 | 3.80:1 | Below floor |
These are the measured token pairs this component draws colour from, not a measurement of the component itself.
API reference
Prop
Type
Generated from SliderProps in registry/bases/base/slider.tsx.
value and onValueChange make this controlled, so the caller holds the number.
label is required and a missing one raises a development warning. min
defaults to 0, max to 100 and step to 1.
Related
- ReadingInput is where a clinical measurement is typed, with the unit and the plausibility checks.
- NumberField is for a precise non-clinical quantity where the digits matter.
- SegmentedControl picks a named option rather than a value along a continuum.