DocumentationComponentsTheme Creator
Theme CreatorGitHubIntroduction
InstallationUsageThemingMotion
Components
CheckboxRadioSwitchTextareaNumberFieldOTP InputSliderCircularSliderRatingSelectPickerComboboxSearchSearchSuggestTokenizerSegmentedControl
AI agentsContributing
  1. Components
  2. Slider

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.

$ pnpm add @tessinaui/ui

When to use

  • An approximate value on a known range: volume, brightness, opacity. The user cares where the thumb sits, not the exact number.
  • A change the user wants to see as they make it. Drag feedback is the point; pair it with valueDisplay when the number also matters.
  • A span between two values. Pass an array and each entry gets a thumb, with minStepsBetweenValues keeping them apart.
  • Adjusting around a neutral point. fillFrom="center" anchors the fill at an origin for bipolar settings.
  • Named stops instead of numbers. Use marks with getValueLabel and getAriaValueText so the control speaks the word, not an index.

When not to use

  • When the exact number matters. Use NumberField, or add one beside the slider (With number input).
  • When the value is not a number. Use Select for a list of choices, or DatePicker for dates.
  • For a range that is very wide or very narrow. A wide one makes every position imprecise; a narrow one is better as SegmentedControl or Rating.
  • To show a value the user cannot change. Use Progress or Meter; readOnly is for a value that is frozen for now, not a readout.
  • For a small step up or down. Use Stepper.

Installation

Terminal
pnpm add @tessinaui/ui

Usage

example.tsx
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

Error message

errorMessage renders under the control, implies intent="error" and joins every thumb's aria-describedby, so each end announces the problem. It is announced politely and suppressed under readOnly.

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" (the 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 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 two handles that read on a capsule (variant="continuous"): outline for the visible knob, bar for a scrubber line at the fill's edge.

Scrubber

The control a photo editor sets a number with: the value sits inside the fill at the leading edge and the line stands where the fill ends, so the bar is the readout. variant="continuous" + thumbVariant="bar", with the fill and line stepped down to a settings rail's own chrome.

The bar holds its own value for the length of a drag, so it follows the pointer even when what it drives updates on a throttle, and the readout is aria-hidden because the thumb announces the same string through getAriaValueText.

With number input

The 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

Skeleton

RTL

A horizontal slider mirrors under dir="rtl": the minimum sits at the right edge, the fill grows leftward, and the leading icon and minLabel swap sides. Arrow keys follow the visual direction, and vertical sliders are unaffected. Unset, dir is inherited from the page, arrow keys included.

API Reference

PropTypeDefaultDescription
valuenumber | readonly number[]—Controlled value. Array = one thumb per entry (any count).
defaultValuenumber | readonly number[]40Initial uncontrolled value.
min / max / stepnumber0 / 100 / 1Range and increment.
largeStepnumber10Page Up/Down and Shift+Arrow step.
minStepsBetweenValuesnumber0Minimum 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"inheritedExplicit wins; otherwise inherited from Surface.
orientation"horizontal" | "vertical""horizontal"
disabledbooleanfalseNot focusable, not announced.
readOnlybooleanfalseFocusable and announced, value frozen.
rounded"none" | "sm" | "md" | "lg" | "xl" | "full""full"Corner radius of the track, fill and value tooltip, one token step per value. The thumb is round at every value.
thumbVariant"default" | "bar" | "outline""default"Handle shape. On continuous, outline renders the visible knob and bar a scrubber line.
fillFrom"start" | "center" | number"start"Fill anchor (single-thumb); non-start paints an origin marker.
segmentedbooleanfalseGap at each interior step (discrete, 2–20 segments).
marksboolean | SliderMark[]falsetrue = every step (thinned >50); array = custom.
markVariant"dot" | "line" | "ruler""dot"ruler auto-generates 41 minor/major hashes when marks is unset.
minLabel / maxLabelReactNode—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").
labelReactNode—Accessible name. Sr-only unless labelPlacement="top".
labelPlacement"hidden" | "top""hidden"Visible label row (holds the text value display).
leadingIcon / trailingIconReactNode—End icons.
iconPlacement"outside" | "in-track""outside"in-track sits inside the capsule (continuous only).
getAriaLabel(index) => string—Per-thumb name in range mode.
getAriaValueText(formatted, value, index) => string—Spoken value.
namestring—Form field name.
formatIntl.NumberFormatOptions—Number formatting (display + aria-valuetext).
localeIntl.LocalesArgument—Formatting locale.
errorMessageReactNode—Error row under the control. Implies intent="error", joins every thumb's aria-describedby, announced politely; suppressed under readOnly.
dir"ltr" | "rtl"—Wired through Base UI's DirectionProvider — layout AND keyboard flip together. Unset, it inherits the page's direction.
classNamestring—Root classes.

Deprecated (kept one minor)

OldUse instead
showValuevalueDisplay="tooltip"
valueInThumbvalueDisplay="thumb"

An explicit valueDisplay always wins over the aliases; each alias warns once in dev.

Migration notes (0.x → this release)

  • Radius correction: rounded="sm"/"md"/"lg" previously rendered one to two steps larger (md was 12px). They now sit on the radius token scale (md = 6px); xl (12px) restores the old md look.
  • SliderSkeleton is silent by default — pass label to announce.
  • dir is no longer forced to "ltr": unset, the slider inherits the page's direction. Pass dir="ltr" to keep a left-to-right slider on an RTL page.

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

KeyAction
Tab / Shift+TabMove 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 / EndMin / max (range: clamped to the neighbouring thumb ± minStepsBetweenValues)

Screen readers

  • label feeds aria-labelledby; use getAriaLabel for distinct per-thumb names in range mode ("Minimum price", "Maximum price").
  • aria-valuetext comes from format/locale, or getAriaValueText for word values — the rating example speaks "Okay" while showing it.
  • Mark labels are aria-hidden visual anchors; the spoken value is the aria-valuetext, never the tick text.
  • readOnly keeps the thumb focusable and announced, so the value can still be read — disabled removes it from the tab order entirely.

Touch

Every thumb has an invisible hit area centred on it: 44×44px on touch screens and 24×24px with a mouse, so even the 12px xs thumb clears both floors. On touch the rail itself is at least 44px tall too, and pressing it anywhere moves the nearest thumb there. Continuous capsules are at least 24px tall at every size.

  • Two range thumbs closer than the hit area share it. The press goes to the thumb moved last (at rest, the later one), so the other is reached by pressing the rail beyond the overlap. When both ends must stay easy to grab on a phone, keep them 44px apart with minStepsBetweenValues.
  • At either end of the track the touch hit area reaches past the slider, by half of what the thumb lacks of 44px: 12px for the default md thumb, 20px for a bar. Against a scrolling edge on a phone, leave that much room beside the slider, or a thumb dragged to the end scrolls the page sideways.

Contrast

The fill inherits the shared intent tokens and the track is bg-secondary, and three of those pairs do not clear the 3:1 that WCAG 1.4.11 asks of a meaningful graphic. Measured by audit:contrast, both themes:

FillLightDark
none (primary)14.2:114.5:1
info4.1:15.7:1
error3.8:12.8:1
success2.6:16.8:1
warning1.7:18.9:1

That is the intent palette's own ceiling, accepted library-wide, not a Slider tuning miss, and the thumb does not rescue it: on the default discrete track it paints the same fill, and a continuous capsule's default thumb is an invisible drag target. With valueDisplay defaulting to "none", a slider in one of those intents should show its value in words: "text", "tooltip-always" and, on a discrete track, "thumb" stay on screen, while "tooltip" appears only while the thumb is hovered, dragged or focused.

AT matrix

ATStatus
VoiceOver + Safari (macOS)Passed 2026-09-14. Focus reads "40, Volume, slider" inside "Volume, group"; Right arrow reads "41", "42"; End reads "100". Transcript: tests/qa/vo-transcripts/slider.md
NVDA + Chrome (Windows)UNTESTED — no Windows on the bench
JAWSUNTESTED

NVDA and JAWS stay untested until a Windows host exists.

OTP InputCircularSlider

On this page

When to useWhen not to useInstallationUsageExamplesDefaultVariantsSizesIntentsError messageOn-color toneRangeCenter-origin fillValue displayValue in thumbMarksRulerRatingVertical dimmerSegmentedThumb variantsScrubberWith number inputWith histogramPresetsExternal steppersNon-linear stopsWith iconsSkeletonRTLAPI ReferenceDeprecated (kept one minor)Migration notes (0.x → this release)Agent notesAccessibilityKeyboardScreen readersTouchContrastAT matrix