Browse the docs
Slider
A real <input type="range"> with the track and thumb restyled. Keyboard
support, touch dragging, min, max, step and form serialization all come
from the browser — the component only changes how it looks.
Props
| Prop | Values | Default | Description |
|---|---|---|---|
size | "sm", "md", "lg" | "md" | Track height and thumb diameter. |
className | string | "" | Appended after the component's own classes. |
Every <input> attribute is forwarded — min, max, step, value,
defaultValue, onChange, disabled, ref.
Common patterns
// Uncontrolled, which is usually enough
<Slider defaultValue={60} />
// Controlled, when the value is shown elsewhere
const [volume, setVolume] = useState(60)
<Slider value={volume} onChange={(e) => setVolume(Number(e.target.value))} />
<span>{volume}%</span>
// Discrete steps
<Slider min={1} max={5} step={1} defaultValue={3} />Notes
Always pair it with a Label via htmlFor and id, or pass
aria-label. On its own a range input announces only its number.
The thumb is styled twice — once for ::-webkit-slider-thumb and once for
::-moz-range-thumb. The two pseudo-elements do not inherit from each other
and a selector list containing an unknown one is dropped whole, so they cannot
be merged. If you restyle the thumb, change both.
The track does not fill in behind the thumb. Doing that without JavaScript
needs a background: linear-gradient(...) recomputed from the value, which
would mean either an inline style per render or a CSS custom property you set
in onChange:
<Slider
style={{
background: `linear-gradient(to right,
var(--color-accent) ${volume}%,
var(--color-border) ${volume}%)`,
}}
value={volume}
onChange={(e) => setVolume(Number(e.target.value))}
/>