Slider
A range input built on Base UI. Discrete and continuous variants, five intents, five sizes, both tones, horizontal/vertical, N-thumb ranges, center-origin fill, dot/line/ruler marks, five value-display modes, and full RTL.
Playground
Installation
pnpm add @tessinaui/uiUsage
import { Slider } from "@tessinaui/ui";
<Slider label="Volume" defaultValue={40} />Always pass label — it names the control for assistive tech (sr-only by
default, visible with labelPlacement="top").
Examples
Default
Variants
discrete thin-track and continuous capsule fill.
Sizes
Five scales, xs through xl.
Intents
On-color tone
On a coloured plate the slider inherits tone="on-color" from the enclosing
Surface and paints from the surface ink — legible on any fill by
construction. The capsule also shows iconPlacement="in-track" (M3's inset
icon: continuous variant only).
Range
Array value → one thumb per entry, any count. minStepsBetweenValues keeps
thumbs from touching.
Center-origin fill
fillFrom="center" (or any value) anchors the fill at an origin instead of the
min end and paints an origin marker — for bipolar ± adjustments.
Value display
valueDisplay: tooltip on interaction, tooltip-always persistent, text
static (pairs with labelPlacement="top"), plus Carbon-style readOnly.
Value in thumb
valueDisplay="thumb" grows the handle into a value pill.
Marks
Dot marks with labels, and the line variant.
Ruler
markVariant="ruler" auto-generates a dense minor/major ruler.
Rating
Discrete stops + a word value via getValueLabel and getAriaValueText.
Vertical dimmer
Segmented
segmented splits the track at each step.
Thumb variants
bar faders, outline rings, and the visible capsule knob
(variant="continuous" + thumbVariant="outline").
With number input
Carbon's default anatomy: slider + synced field, field directly after the slider in the tab order.
With histogram
Presets
External steppers
Non-linear stops
Base UI has no scale transform, so non-linear sliders run in index space over
named stops — the mapping lives in marks, getValueLabel and
getAriaValueText.
With icons
Props
| Prop | Type | Default | Description |
|---|---|---|---|
value | number | readonly number[] | — | Controlled value. Array = one thumb per entry (any count). |
defaultValue | number | readonly number[] | 40 | Initial uncontrolled value. |
min / max / step | number | 0 / 100 / 1 | Range and increment. |
largeStep | number | 10 | Page Up/Down and Shift+Arrow step. |
minStepsBetweenValues | number | 0 | Minimum steps between range thumbs. |
thumbCollisionBehavior | "push" | "swap" | "none" | "push" | Colliding-thumb behaviour. |
onValueChange | (value, eventDetails) => void | — | Every change; eventDetails.reason is 'drag' | 'keyboard' | 'track-press' | …, plus activeThumbIndex. |
onValueCommitted | (value, eventDetails) => void | — | Pointer up / key up. |
variant | "discrete" | "continuous" | "discrete" | Thin track vs capsule. |
size | "xs" | "sm" | "md" | "lg" | "xl" | "md" | Scale. |
intent | "none" | "error" | "warning" | "success" | "info" | "none" | Semantic colour. |
tone | "default" | "on-color" | inherited | Explicit wins; otherwise inherited from Surface. |
orientation | "horizontal" | "vertical" | "horizontal" | |
disabled | boolean | false | Not focusable, not announced. |
readOnly | boolean | false | Focusable and announced, value frozen (Carbon semantics). |
rounded | "none" | "sm" | "md" | "lg" | "xl" | "full" | "full" | §2 canon scale on track, fill and thumb. |
thumbVariant | "default" | "bar" | "outline" | "default" | Handle shape. On continuous, outline renders the visible knob. |
fillFrom | "start" | "center" | number | "start" | Fill anchor (single-thumb); non-start paints an origin marker. |
segmented | boolean | false | Gap at each interior step (discrete, 2–20 segments). |
marks | boolean | SliderMark[] | false | true = every step (thinned >50); array = custom. |
markVariant | "dot" | "line" | "ruler" | "dot" | ruler auto-generates 41 minor/major hashes when marks is unset. |
minLabel / maxLabel | ReactNode | — | Static end labels. |
valueDisplay | "none" | "tooltip" | "tooltip-always" | "text" | "thumb" | "none" | How the live value shows. |
getValueLabel | (value) => ReactNode | — | Custom value rendering ("Okay", "2,500 ml"). |
label | ReactNode | — | Accessible name. Sr-only unless labelPlacement="top". |
labelPlacement | "hidden" | "top" | "hidden" | Visible label row (holds the text value display). |
leadingIcon / trailingIcon | ReactNode | — | End icons. |
iconPlacement | "outside" | "in-track" | "outside" | in-track sits inside the capsule (continuous only, per M3). |
getAriaLabel | (index) => string | — | Per-thumb name in range mode. |
getAriaValueText | (formatted, value, index) => string | — | Spoken value. |
name | string | — | Form field name. |
format | Intl.NumberFormatOptions | — | Number formatting (display + aria-valuetext). |
locale | Intl.LocalesArgument | — | Formatting locale. |
dir | "ltr" | "rtl" | "ltr" | Wired through Base UI's DirectionProvider — layout AND keyboard flip together. |
className | string | — | Root classes. |
Deprecated (kept one minor)
| Old | Use instead |
|---|---|
showValue | valueDisplay="tooltip" |
valueInThumb | valueDisplay="thumb" |
An explicit valueDisplay always wins over the aliases; each alias warns once
in dev.
Migration notes (0.x → this release)
- Radius correction (§2):
rounded="sm"/"md"/"lg"previously rendered one to two steps larger (mdwas 12px). They now sit on the canon token scale (md= 6px);xl(12px) restores the oldmdlook. SliderSkeletonis silent by default — passlabelto announce.
Agent notes
Stable hooks for tooling and tests: data-component="slider" on the root, and
data-slot on every part — slider, slider-control, slider-track,
slider-fill, slider-thumb, slider-value, slider-tooltip,
slider-marks, slider-segment-gap, slider-origin, slider-label,
slider-min-label, slider-max-label, slider-leading-icon,
slider-trailing-icon. data-readonly marks the frozen state;
data-dragging (Base UI) marks an active drag.
Accessibility
Implements the WAI-ARIA Slider pattern (multi-thumb per the Slider
(Multi-Thumb) pattern) on Base UI's primitive — each thumb is a real
<input type="range">.
Keyboard
| Key | Action |
|---|---|
Tab / Shift+Tab | Move between thumbs (tab order never re-sorts by value) |
Arrow Right / Arrow Up | +step (Right/Left flip in RTL) |
Arrow Left / Arrow Down | −step |
Shift+Arrow | ±largeStep |
Page Up / Page Down | ±largeStep |
Home / End | Min / max (range: clamped to the neighbouring thumb ± minStepsBetweenValues) |
Screen readers
labelfeedsaria-labelledby; usegetAriaLabelfor distinct per-thumb names in range mode ("Minimum price", "Maximum price").aria-valuetextcomes fromformat/locale, orgetAriaValueTextfor word values — the rating example speaks "Okay" while showing it.- Mark labels are
aria-hiddenvisual anchors; the spoken value is thearia-valuetext, never the tick text. readOnlykeeps the thumb focusable and announced (Carbon's read-only rationale) —disabledremoves it from the tab order entirely.
Touch
44×44px hit area centred on every thumb, and the rail itself floors to 44px on
touch devices (touch:min-h-11 — the §5 different-property pairing). Continuous
capsules are ≥24px tall at every size.
AT matrix
| AT | Status |
|---|---|
| VoiceOver + Safari (macOS) | pending this release's L3 pass |
| NVDA + Chrome (Windows) | UNTESTED — no Windows on the bench |
| JAWS | UNTESTED |
Status stays beta until one VoiceOver and one NVDA pass are on record (§5.6).