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

PropValuesDefaultDescription
size"sm", "md", "lg""md"Track height and thumb diameter.
classNamestring""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))}
/>