Slider
since v8.6.0Range-input primitive for selecting a value (single thumb) or a bounded
range (two thumbs). Wraps @radix-ui/react-slider (per
docs/adr/0001-headless-primitive-baseline.md) — Radix owns role="slider",
aria-valuemin/max/now, keyboard (Arrows, Home/End, Page Up/Down), RTL,
and thumb collision; we own the public API shape and the visual treatment.
The mode is inferred from the value shape: a number is single-thumb, a
[number, number] tuple is a range. onValueChange and onValueCommit
both echo that shape.
Out-of-range values are clamped and reversed range tuples are sorted before
reaching Radix, so aria-valuenow/min/max always stay coherent. Pass
aria-valuetext (a string, or a (value) => string formatter for per-thumb
text) to expose formatted values; it is routed to the thumb(s), not the root.
Like any uncontrolled input, defaultValue is read once at mount: changing
its shape (number ↔ tuple) afterward will not re-seed the thumbs. Use the
controlled value prop if the mode can change at runtime.
Install
Add the package and import the component.
pnpm add @hey-mike/tungstenpnpm add @hey-mike/tungstenimport { Slider } from '@hey-mike/tungsten';import { Slider } from '@hey-mike/tungsten';Preview
Same fixtures used by the visual-regression suite.
Usage
apps/docs/app/snapshots/slider/page.tsx
'use client';
import { Slider } from '@hey-mike/tungsten';
import { VariantGrid } from '../_components/VariantGrid';
export default function SliderSnapshot() {
return (
<VariantGrid
title="Slider"
variants={[
{
label: 'single',
node: (
<div className="w-64">
<Slider label="Volume" defaultValue={40} />
</div>
),
},
{
label: 'range',
node: (
<div className="w-64">
<Slider label="Price" defaultValue={[25, 75]} />
</div>
),
},
{
label: 'stepped',
node: (
<div className="w-64">
<Slider label="Zoom" defaultValue={60} step={10} />
</div>
),
},
{
label: 'disabled',
node: (
<div className="w-64">
<Slider label="Locked" defaultValue={50} disabled />
</div>
),
},
]}
/>
);
}
'use client';
import { Slider } from '@hey-mike/tungsten';
import { VariantGrid } from '../_components/VariantGrid';
export default function SliderSnapshot() {
return (
<VariantGrid
title="Slider"
variants={[
{
label: 'single',
node: (
<div className="w-64">
<Slider label="Volume" defaultValue={40} />
</div>
),
},
{
label: 'range',
node: (
<div className="w-64">
<Slider label="Price" defaultValue={[25, 75]} />
</div>
),
},
{
label: 'stepped',
node: (
<div className="w-64">
<Slider label="Zoom" defaultValue={60} step={10} />
</div>
),
},
{
label: 'disabled',
node: (
<div className="w-64">
<Slider label="Locked" defaultValue={50} disabled />
</div>
),
},
]}
/>
);
}
Props
Surface specific to <Slider />.
| Prop | Type | Default | Description |
|---|---|---|---|
| value | SliderValue | — | Current value. A number renders one thumb; a [min, max] tuple renders a range. |
| defaultValue | SliderValue | — | Initial value in uncontrolled mode. Defaults to a single thumb at min. |
| onValueChange | ((value: SliderValue) => void) | — | Fires with the next value, matching the shape of value/defaultValue. |
| onValueCommit | ((value: SliderValue) => void) | — | Fires when interaction settles, matching the shape of value/defaultValue. |
| aria-valuetext | string | ((value: number) => string) | — | Accessible text for the value, routed to each thumb (not the root). Pass a
string for a single shared label, or a function to format per thumb value
(e.g. (v) => $${v}``). |
| label* | string | — | Accessible name applied to the thumb(s). Range thumbs get "(minimum)"/"(maximum)" suffixes. |
| min | number | 0 | — |
| max | number | 100 | — |
| step | number | 1 | — |
Used in recipes
Compositions from the /recipes reference that use this component.