Slider

Pick one value from a continuous range, where the handle's position is the answer and the exact number is secondary. If the number is what matters — a price, a timeout, a quantity someone would rather type — that is a number Input, or a slider paired with one. The base is the native input[type=range], which is the only version that is correct with no runtime: dragging, arrow keys, and the value announcement all arrive free.

Slider · In context
60

Drag it, or focus it and press the arrow keys — the movement, the stepping, and the value announcement are all the native element's. This one carries step="5", so it snaps; the rows below show the other two cases. Only the filled track is scripted, standing in for the layer-2 behaviour the contract describes: --ds-slider-fill cannot follow the thumb on its own. The number beside the label is host text.

Rail

The one axis, and it moves one thing: how thick the groove is. The handle stays the same pill at every rail — a knob that grew with its groove would make the axis mean two things, and the thick rail's whole point is that the groove becomes a container the handle sits inside. Thick also drops to radius.control, since a full radius at that height bends the rail into a lozenge. Every rail sets only --ds-slider-rail; the track height and the handle’s centring offset both derive from it, so the knob cannot come off-centre when a rail changes.

ExampleRailModifierHeightUsage
Thin--thinspace.1Dense rows where the slider is one control among many
Defaultds-sliderspace.1-5Everywhere else — the same groove Progress uses
Thick--thicksize.control.smMatches the small button’s height so the two sit on one row; the fill drops to half presence so the groove does not outshout the handle

Range and step

Stepped or continuous is the native step attribute, never a modifier — the same call Input makes for type. The trap is the middle row: omitting step is not continuous, it is step 1, which merely looks continuous across a wide range.

ExampleAttributeBehavesUsage
step="any"ContinuousTruly analogue values — opacity, a volume mix, anything with no valid increment
step omittedSteps by 1The default, and the one to be careful with — it reads as continuous but is not
step="5"Snaps to fivesWhen only some values are meaningful — the canvas above uses this

States

The handle is a solid action.primary.bg mark with no ring — dark on a light theme, light on a dark one. It shares the filled action.primary.hover and action.primary.active ladder with Button, so the system has one state direction for every primary fill. The focus ring sits on the thumb rather than the input box — the same placement exception text-entry controls take.

ExampleStateDriverHandleUsage
Restdefaultaction.primary.bgA solid mark — dark on light, light on dark, no ring
Hover:hoveraction.primary.hoverThe handle steps; the track does not move
Active:activeaction.primary.activeGrabbed — held for the whole drag
Focus:focus-visiblefocus.ringRing on the thumb, not the full-width box
Disabled:disabledborder.strongHandle and filled track both step back — an inert control should not carry the loudest mark on the page

Keyboard

Every key below is the native element's, not a script's — which is the whole argument for building on it.

KeyDoes
Decrease by one step
Increase by one step
HomeJump to min
EndJump to max
TabMove to and from the slider as a single stop — the thumb is never a separate one

Usage notes

A slider says "somewhere along this range" — the number is the host's to show.

VerdictRule
DoUse a slider only when the position is the answer; when the exact number matters, use a number Input or pair the slider with one.
DoName every slider through a FormField label or aria-label, and let the native element report its own value — never re-announce the number with a live region.
DoSet a step that matches the decision; a continuous-feeling slider over whole-unit values invites a number the host will silently round.
Don’tSet --ds-slider-fill without keeping it in sync with the value — a fill that disagrees with the handle is worse than the plain track the default ships.
Don’tRebuild a slider out of divs to get a range, ticks, or a value bubble. Those are layer-2 additions over this contract, not a replacement for the native element and everything it brings.