Components / forms

Slider

Radix is permitted here — Slider is on the closed list of six.

Rendered from the package · open on its own

Summary

A value picked by position rather than typing.

APG patternSlider, Multi-Thumb Slider
Built onRadix
Statusstable
Since0.1.0

Pass a number for one thumb, an array for a range.

<Slider id="price" label="Price range" defaultValue={[40, 120]} min={0} max={200} step={10}
  valueText="£40 – £120" thumbLabels={['Minimum price', 'Maximum price']} minStepsBetweenThumbs={1} />

Anatomy

A track with a filled range and one or two thumbs, plus a header carrying the label and the current value.

PartElementRequired
header<div>no
label<span>no
range<radixslider.range>yes
root<radixslider.root>yes
thumb<radixslider.thumb>yes
track<radixslider.track>yes
value<span>yes

Properties

PropertyValuesDefaultWhat it does
valuenumber | number[]noneA number for one thumb, an array for a range.
defaultValuenumber | number[]50A number for one thumb, a pair for a range. The shape of this decides which the slider is.
onValueChange(value: number | number[]) => voidnoneCalled as the thumb moves, with a number or a pair to match value.
minnumber0The bottom of the range.
maxnumber100The top of the range.
stepnumber1How far one arrow key moves the thumb. Anything a reader would type instead wants a Spinbutton.
labelReact.ReactNodenoneDrawn above the track and used as the group's accessible name. Each thumb still needs its own — see thumbLabels.
valueTextstring | string[]noneOverrides the printed value AND is exposed as aria-valuetext, so a screen reader hears "40 pixels" rather than "40". Pass an array to give each thumb of a range its own text.
thumbLabelsstring[]noneOne accessible name per thumb. Required for a range: two controls both called "Price" are indistinguishable by keyboard or screen reader.
minStepsBetweenThumbsnumbernoneSteps that must remain between adjacent thumbs. Thumbs never cross — they push — so without this a range can be dragged down to zero width.
disabledbooleannoneRemoves the thumbs from the tab order.
idstringnoneOptional. A stable id is generated when omitted; the name never depends on it.

States

StateExpressed as
Pressed:active
Focused:focus-visible
Hover:hover
Disableddata-disabled

Behavior

Arrow keys move by step, Home and End go to the ends. A range's thumbs cannot cross, and minStepsBetweenThumbs keeps a gap between them.

Accessibility

Each thumb needs its own name. Minimum price and Maximum price — two thumbs both called "Price range" are indistinguishable to anyone not looking at them.

valueText is what a reader hears. The raw number is rarely the unit they think in.

No vertical orientation. That is a capability gap rather than an oversight, and it is recorded in the upstream port rather than hidden in a test.

Keyboard

KeyResult
Arrow keysMove the thumb by one step
Home / EndJump to the minimum or maximum
Page Up / Page DownMove by a larger increment

Roles, states and properties

AttributeSet by
aria-labelledbyset at render
aria-valuetextset at render

Focus

Each thumb is its own tab stop, which is why each needs its own name. Radix supplies the roving behavior; nothing here re-implements it.

Screen reader

Not recorded as verified. Stage 4 of the readiness ladder — driving each component with VoiceOver and NVDA — is unstarted across the package, and no component should claim otherwise until docs/journal/ records the session that did it.

Contrast and target size

npm run check:contrast audits every color pair this component uses across all four skins, light and dark, and fails the build on a shortfall. Two waivers are on the record in docs/audit/DEFERRED.md.

It takes the touch floor under (pointer: coarse) — the visual size is unchanged on a desktop pointer.

Tested

  • test/apg/batch-a.test.jsx
  • test/apg/partials-sweep.test.jsx

Known gaps

  • 3 upstream refs — APG's own page or markup, not the pattern
  • 1 upstream ref — covered by our own dependent-range tests in partials-sweep
  • 1 upstream ref — Slider has no vertical orientation. A capability gap, not a defect — nothing in the system asks for one yet.
  • 4 upstream refs — sliders drawn as SVG rails

Appearance

TokenUsed for
--se-accentbackground, border
--se-border-widthborder
--se-duration-instanttransition
--se-ease-standardtransition
--se-focus-coloroutline
--se-focus-offsetoutline-offset
--se-focus-widthoutline
--se-font-codefont-family
--se-radius-pillborder-radius
--se-shadow-floatingbox-shadow
--se-shadow-raisedbox-shadow
--se-space-2block-size, gap
--se-space-3gap
--se-surfacebackground
--se-surface-sunkbackground
--se-target-minmin-block-size
--se-textcolor
--se-text-disabledbackground, border-color
--se-text-mutedcolor
--se-text-smfont-size
--se-thumb-sizeblock-size, inline-size
--se-weight-mediumfont-weight

The thumb takes --se-shadow-raised at rest and --se-shadow-floating on hover. That is literal lift under a pointer and says nothing about dismissal — the one place a level means something other than what 3.5 reads.

Motion

The thumb's shadow crosses --se-duration-instant. The fill does not animate while dragging; it tracks the pointer.

Content

The label names the quantity. valueText carries the unit and the currency — the number alone is ambiguous the moment there is more than one slider on the page.

Usage

Use it when the precise figure barely matters. For an exact, small-range number the reader knows, a Spinbutton reads faster — and when both are true, show both and let them share state.

When not to use it. For a value with fewer than about five useful positions. That is a RadioGroup.

Notes

Radix is permitted here — Slider is on the closed list of six.