Range Slider
Two thumbs over one track. Slider is a restyled native input[type=range], which has exactly one thumb by construction — so a range needs a custom control with the ARIA written out.
src/components/ui/range-slider.tsxAbout
From the component's own source, where it was written.
Two thumbs over one track, for a minimum and maximum.
Slider is a restyled native input[type=range], which has exactly one thumb by construction — a range cannot be built from it, so this is a custom control with the ARIA and keyboard behaviour written out.
Track and thumb come from the shared sliderSize scale, which sets --slider-track and --slider-thumb — so a range slider and a plain one of the same size line up exactly.
Both thumbs are real buttons with role="slider", so each is a tab stop with its own value and its own bounds. A single element reporting two numbers has no way to be announced or operated.
The thumbs can cross: dragging the low one past the high one swaps which is which rather than stopping dead at the other. Clamping is the more common implementation and it feels broken — the pointer keeps moving while the thumb stays put.
Install
npx astralyx-ui add range-slider
Usage
import { RangeSlider } from '@/components/ui/range-slider'
<RangeSlider min={0} max={1000} step={10} value={range} onValueChange={setRange} />Composer
Change the props and the source below follows.
<RangeSlider
min={0}
max={1000}
step={10}
value={range}
onValueChange={setRange}
showValues
/>Examples
Price range
<RangeSlider min={0} max={1000} step={10} showValues />API
Generated from the source, so it cannot fall behind the code. One table per exported component, then hooks, functions and types.
RangeSlider
Extends Omit<ComponentProps<'div'>, 'defaultValue' | 'onChange'>.
| Prop | Type | Default | Description |
|---|---|---|---|
| min | number | 0 | — |
| max | number | 100 | — |
| step | number | 1 | — |
| value | RangeValue | — | Always sorted low-to-high on write. |
| defaultValue | RangeValue | [25, 75] | — |
| onValueChange | (value: RangeValue) => void | — | Always sorted low-to-high on write. |
| onValueCommit | (value: RangeValue) => void | — | Fires once on release, for anything expensive to run per frame. |
| disabled | boolean | false | — |
| size | keyof typeof sliderSize | 'default' | Reads the shared sliderSize tokens, so it lines up with a plain Slider of the same size. |
| label | string | — | — |
| formatValue | (value: number) => string | DEFAULT_FORMAT_VALUE | — |
| showValues | boolean | false | — |
Types
RangeValue
Two thumbs over one track, for a minimum and maximum.
| Field | Type | Description |
|---|---|---|
| 0 | number | — |
| 1 | number | — |
| length | 2 | — |
Behaviour
| Topic | Detail | Description |
|---|---|---|
| crossing | thumbs swap | Dragging the low thumb past the high one swaps which is which. Clamping is more common and feels broken — the pointer keeps moving while the thumb stays put. |
| accessibility | two sliders | Each thumb is a button with role="slider" and its own bounds, so both are tab stops and both announce. |