Primitives Forms
Slider
The native range input. The browser provides dragging, the keyboard, min, max and step, the form value and the slider role; the stylesheet draws the track and the thumb from the tokens. Tier 0.
- Tier
- 0 · Native
- Behavior JS
- 0 B brotli · 0 B gzip ·
fixtures/· methodresults.json (native-only) - Platform features
<input type="range">,::-webkit-slider-thumb,::-moz-range-progress,accent-color,writing-mode- Shared listeners
- none
- Per-instance listeners
- none
- Lazy state
- none
- Native base
<input type="range">
On this page
Example
<label for="slider-volume">Volume</label>
<input id="slider-volume" type="range" name="volume" min="0" max="100" step="5" value="40"
data-slean="slider" />
<label for="slider-temperature">Temperature</label>
<input id="slider-temperature" type="range" name="temperature" min="16" max="28" step="0.5"
value="21" data-slean="slider" /><script lang="ts">
// Nothing to import: the browser owns the range input. The stylesheet is optional.
import '@svelte-lean/styles/slider.css';
let volume = $state(40);
let temperature = $state(21);
</script>
<label for="slider-volume">Volume</label>
<input id="slider-volume" type="range" name="volume" min="0" max="100" step="5"
bind:value={volume} data-slean="slider" />
<output for="slider-volume">{volume}</output>
<label for="slider-temperature">Temperature</label>
<input id="slider-temperature" type="range" name="temperature" min="16" max="28" step="0.5"
bind:value={temperature} data-slean="slider" />
<output for="slider-temperature">{temperature} °C</output>Why this implementation exists
<input type="range"> is a complete slider: pointer and touch dragging, the arrow, Page, Home and End keys, min, max and step, the value in the form, and the slider role with its value, range and orientation exposed to assistive technology. A slider component rebuilds each of these with pointer events and ARIA.
What the platform leaves open is the look. The stylesheet draws the track and the thumb through the engines’ pseudo-elements, and here the gap is honest: only Firefox styles the filled part of the track (::-moz-range-progress). Chrome and Safari have no such pseudo-element, and filling the track there needs script that reads the value, so the styled slider shows the thumb on an unfilled track. data-variant="native" keeps the browser’s own drawing instead, which Chrome and Firefox draw with a filled track.
The browser owns
- pointer dragging, min, max and step
- the arrow, Page, Home and End keys
- the slider role with its value, range and orientation
- the form value and the input and change events
- the filled track in Firefox, and on its own drawing the filled track and datalist tick marks
Svelte Lean owns
- slider.css: the track and thumb from the tokens, the hover halo, the native variant, the vertical option
- the contract
- documentation
Usage
The markup needs no package. Install @svelte-lean/styles for the stylesheet; @svelte-lean/primitives adds the typed contract and nothing at runtime.
npm install @svelte-lean/stylespnpm add @svelte-lean/stylesyarn add @svelte-lean/stylesbun add @svelte-lean/stylesimport '@svelte-lean/styles/slider.css';Put data-slean="slider" on an <input type="range"> with a label, min, max, step and value. Bind the value with bind:value; it is a number.
Show the exact number next to the slider with an <output for> when it matters. Add aria-valuetext when the number alone does not describe the value.
data-variant="native" for the platform’s drawing with the filled track and tick marks; data-slean-orientation="vertical" for a vertical slider. For two thumbs, use Range slider.
Anatomy
| Part | Element | data-slean-part | Required | Notes |
|---|---|---|---|---|
| root | <input type="range" data-slean="slider"> | – | yes | A label; min, max, step, value. data-variant="native" keeps the platform drawing; data-slean-orientation="vertical". |
| marks | <datalist id="…"><option value="…"></option></datalist> | – | no | Named by the list attribute; tick marks on the native variant only. |
Runtime profile
Tier 0: the slider has no behavior module, the Vite plugin maps slider to no module, and the native-only fixture proves the path ships no Svelte Lean JavaScript.
Accessibility contract
- The platform exposes the slider role, the value, the minimum, the maximum and the orientation from the input itself; no ARIA attribute is needed for them.
- The arrow keys change the value by
step, PageUp and PageDown by a larger amount, Home and End go to the bounds; underdir="rtl"ArrowLeft and ArrowRight follow the visual direction. aria-valuetextreplaces the number when the number is not the value’s meaning (a side, a size, a level).- The focus ring surrounds the input on keyboard focus. Under a coarse pointer the input and the thumb grow to the touch target size.
- Forced colors: the track takes the text color and the thumb the Highlight system color.
Keyboard
| Key | When | Result |
|---|---|---|
| ArrowRight/ArrowUp | slider focused | One step up; ArrowRight is one step down under RTL (native) |
| ArrowLeft/ArrowDown | slider focused | One step down; ArrowLeft is one step up under RTL (native) |
| PageUp/PageDown | slider focused | A tenth of the range up or down (native, Chrome) |
| Home/End | slider focused | The minimum, the maximum (native) |
Platform features
| Feature | Baseline | Outside the target |
|---|---|---|
<input type="range"> | Widely available | Not applicable |
Filled track | Firefox only (::-moz-range-progress) | The thumb on an unfilled track; data-variant="native" for the platform’s drawing |
accent-color | Not Baseline: Chrome 93, Firefox 92, Safari 26.2 | The native variant in the platform’s color |
Vertical form controls (writing-mode) | Baseline 2024 (Chrome 124, Firefox 120, Safari 17.4) | The vertical slider is drawn horizontally |
Datalist tick marks | Chrome, Firefox 109, Safari 12.1, on the native drawing only | No tick marks |
Without JavaScript
Fully functional.
Server rendering
The value, min, max and step attributes render the initial state; the thumb is in place before any script runs.
Before hydration
Nothing is attached; the slider works before and after hydration alike.
Styling
slider.css resets the input’s appearance and draws the track and the thumb with ::-webkit-slider-runnable-track and ::-webkit-slider-thumb (Chrome, Safari) and ::-moz-range-track, ::-moz-range-thumb and ::-moz-range-progress (Firefox), with a halo on hover. The native variant restores the platform’s appearance and sets accent-color. The tokens it reads:
| Token | Default (light) | Applies to |
|---|---|---|
--slean-control-height-sm | 2rem | block-size |
--slean-radius-sm | 0.375rem | border-radius |
--slean-accent | oklch(54% 0.19 258) | accent-color, background |
--slean-radius-full | 9999px | border-radius |
--slean-border | var(--slean-neutral-6) | background |
--slean-surface | oklch(100% 0 0) | border |
--slean-shadow-sm | 0 1px 2px oklch(0% 0 0 / 0.08) | box-shadow |
--slean-duration-fast | 100ms | transition |
--slean-ease | cubic-bezier(0.2, 0, 0, 1) | transition |
--slean-accent-soft | oklch(95% 0.03 258) | box-shadow |
--slean-control-height-md | 2.25rem | block-size |
State selectors the stylesheet targets, all from the platform or ARIA: ::-moz-range-progress, ::-moz-range-thumb, ::-moz-range-track, ::-webkit-slider-runnable-track, ::-webkit-slider-thumb, :disabled, :hover.
Variant attributes: data-variant (native); data-slean-orientation (vertical).
Compatibility notes
The range input is widely available. The filled track is drawn by Firefox only; the native variant takes the accent color where accent-color is supported (Chrome 93, Firefox 92, Safari 26.2; not Baseline). Vertical sliders through writing-mode are Baseline 2024. Tick marks appear only on the platform’s drawing.
Examples
The platform’s drawing and tick marks
data-variant="native" keeps the browser’s own slider and colors it with accent-color. Chrome and Firefox draw it with the track filled up to the thumb, and
the browsers draw a tick mark for each option of the <datalist> named by list. The styled track has neither in Chrome and Safari.
<label for="slider-zoom">Zoom</label>
<input id="slider-zoom" type="range" name="zoom" min="50" max="150" step="25" value="100"
list="slider-zoom-marks" data-slean="slider" data-variant="native" />
<datalist id="slider-zoom-marks">
<option value="50"></option>
<option value="75"></option>
<option value="100"></option>
<option value="125"></option>
<option value="150"></option>
</datalist>Vertical
data-slean-orientation="vertical" sets writing-mode: vertical-lr and direction: rtl. The browser then lays the slider out vertically with the minimum at
the bottom, keeps ArrowUp as one step up, and reports the vertical orientation to assistive
technology. The same two declarations work in an application’s own CSS.
<input type="range" name="low" value="60" aria-label="Low"
data-slean="slider" data-slean-orientation="vertical" />
<input type="range" name="mid" value="35" aria-label="Middle"
data-slean="slider" data-slean-orientation="vertical" />
<input type="range" name="high" value="75" aria-label="High"
data-slean="slider" data-slean-orientation="vertical" />/* The vertical option is these two declarations; they work without the attribute. */
input[type='range'].vertical {
writing-mode: vertical-lr;
direction: rtl; /* the minimum at the bottom */
}Value text
When the number does not describe the value, aria-valuetext does; screen readers read
it instead of the number. It changes with the value, so the application keeps it up to date.
<script lang="ts">
let balance = $state(0);
// The number alone does not say which side: the text is the application's job.
const text = $derived(
balance === 0 ? 'Centered' : `${Math.abs(balance)} to the ${balance < 0 ? 'left' : 'right'}`
);
</script>
<label for="balance">Balance</label>
<input id="balance" type="range" min="-10" max="10" bind:value={balance}
aria-valuetext={text} data-slean="slider" />Testing
apps/playground/tests/primitives/forms.spec.tsPlaywright: keyboard, form values, :user-invalid, names and descriptions, axepackages/styles/testsstatic checks of the stylesheet (layers, tokens, specificity, dark parity)fixtures/native-onlyconsumer build asserting that no behavior runtime ships (invariant A)
Source
packages/primitives/src/slider/contract.mdthe contract in prose: semantics, keyboard, focus, ARIA, SSR, JS-disabled behaviorpackages/primitives/src/slider/contract.tstyped constants: name, tier, base, parts, options, eventspackages/styles/css/slider.cssthe optional stylesheet