Skip to main content
A slider allows users to make selections from a range of values. It is a custom <input type="range"> with support for custom styling, accessibility, multi-thumb, vertical orientation, and RTL. Features
  • Supports centered origin (track fills from center to thumb)
  • Full keyboard navigation with proper ARIA
  • Click or touch on track to update value
  • Right-to-left (RTL) support
  • Horizontal and vertical orientations
  • Multi-thumb range selection
  • Prevents text selection while dragging
  • Tick mark rendering

Installation

npm install @zag-js/slider @zag-js/react

Usage

import { useMachine, normalizeProps } from "@zag-js/react"
import * as slider from "@zag-js/slider"
import { useId } from "react"

export function Slider() {
  const service = useMachine(slider.machine, {
    id: useId(),
    defaultValue: [40],
    min: 0,
    max: 100,
  })
  const api = slider.connect(service, normalizeProps)

  return (
    <div {...api.getRootProps()}>
      <div>
        <label {...api.getLabelProps()}>Volume</label>
        <output {...api.getValueTextProps()}>{api.value[0]}</output>
      </div>
      <div {...api.getControlProps()}>
        <div {...api.getTrackProps()}>
          <div {...api.getRangeProps()} />
        </div>
        {api.value.map((_, i) => (
          <div key={i} {...api.getThumbProps({ index: i })}>
            <input {...api.getHiddenInputProps({ index: i })} />
          </div>
        ))}
      </div>
    </div>
  )
}

Setting the initial value

Pass defaultValue as an array. Use a single-element array for a single thumb.
const service = useMachine(slider.machine, {
  id: useId(),
  defaultValue: [30],
})

Controlled slider

Use value and onValueChange to control the value externally.
const [value, setValue] = useState([40])

const service = useMachine(slider.machine, {
  id: useId(),
  value,
  onValueChange(details) {
    // details => { value: number[] }
    setValue(details.value)
  },
})

Min, max, and step

const service = useMachine(slider.machine, {
  id: useId(),
  min: -10,
  max: 10,
  step: 0.5,
})

Vertical orientation

const service = useMachine(slider.machine, {
  id: useId(),
  orientation: "vertical",
})
Apply a height style to the root element when using vertical orientation.

Track origin

Control where the filled track starts with origin:
  • "start" — track fills from start to thumb (default)
  • "center" — track fills from the midpoint (50%) to the thumb; useful for offset/diverging scales
  • "end" — track fills from thumb to the end; useful for threshold sliders
const service = useMachine(slider.machine, {
  id: useId(),
  origin: "center",
})

Multi-thumb range slider

Pass an array with two values for a range slider.
const service = useMachine(slider.machine, {
  id: useId(),
  defaultValue: [20, 80],
})

Thumb collision behavior

For multi-thumb sliders, control what happens when thumbs overlap.
const service = useMachine(slider.machine, {
  id: useId(),
  defaultValue: [20, 80],
  thumbCollisionBehavior: "swap", // "none" | "push" | "swap"
})

Thumb alignment

Control whether the thumb center aligns to the track or stays contained within it.
const service = useMachine(slider.machine, {
  id: useId(),
  thumbAlignment: "contain",
  thumbSize: { width: 20, height: 20 },
})

RTL support

const service = useMachine(slider.machine, {
  id: useId(),
  dir: "rtl",
})

Tick marks

Use api.getMarkerProps() to position markers along the track.
<div {...api.getMarkerGroupProps()}>
  {[0, 25, 50, 75, 100].map((value) => (
    <span key={value} {...api.getMarkerProps({ value })}>
      {value}
    </span>
  ))}
</div>

Usage within forms

Set name and render the hidden input via api.getHiddenInputProps().
const service = useMachine(slider.machine, {
  id: useId(),
  name: "volume",
})

API reference

defaultValue
number[]
The initial value(s) of the slider (uncontrolled).
value
number[]
The controlled value(s). Use with onValueChange.
min
number
The minimum allowed value. Defaults to 0.
max
number
The maximum allowed value. Defaults to 100.
step
number
The step increment. Defaults to 1.
orientation
"horizontal" | "vertical"
The orientation of the slider. Defaults to "horizontal".
origin
"start" | "center" | "end"
Where the track fill originates. Defaults to "start".
thumbAlignment
"center" | "contain"
How the thumb aligns relative to the track bounds. Defaults to "contain".
thumbSize
{ width: number, height: number }
The explicit size of the thumb. Used when thumbAlignment is "contain".
thumbCollisionBehavior
"none" | "push" | "swap"
What happens when two thumbs collide in a multi-thumb slider.
disabled
boolean
Whether the slider is disabled.
invalid
boolean
Whether the slider is in an invalid state.
name
string
The name used for the hidden input in form submissions.
dir
"ltr" | "rtl"
The text direction. Defaults to "ltr".
getAriaValueText
(details: { value: number, index: number }) => string
Returns the aria-valuetext for a given thumb. Used for screen reader announcements.
onValueChange
(details: { value: number[] }) => void
Callback fired while dragging or on keyboard input.
onValueChangeEnd
(details: { value: number[] }) => void
Callback fired when dragging ends or on keyboard commit.
onFocusChange
(details: { focusedIndex: number, value: number[] }) => void
Callback fired when focus moves between thumbs.

Styling

Each part has a data-part attribute. State attributes are added to the relevant parts.

Focused state

[data-part="root"][data-focus] { /* root when any thumb is focused */ }
[data-part="control"][data-focus] { /* control when focused */ }
[data-part="track"][data-focus] { /* track when focused */ }
[data-part="range"][data-focus] { /* range when focused */ }

Disabled state

[data-part="root"][data-disabled] { /* disabled root */ }
[data-part="label"][data-disabled] { /* disabled label */ }
[data-part="thumb"][data-disabled] { /* disabled thumb */ }
[data-part="range"][data-disabled] { /* disabled range */ }

Invalid state

[data-part="root"][data-invalid] { /* invalid root */ }
[data-part="label"][data-invalid] { /* invalid label */ }
[data-part="thumb"][data-invalid] { /* invalid thumb */ }
[data-part="range"][data-invalid] { /* invalid range */ }

Orientation

[data-part="root"][data-orientation="horizontal"] { /* horizontal root */ }
[data-part="root"][data-orientation="vertical"] { /* vertical root */ }
[data-part="track"][data-orientation="horizontal"] { /* horizontal track */ }
[data-part="track"][data-orientation="vertical"] { /* vertical track */ }

Tick marks

[data-part="marker"][data-state="at-value"] { /* marker at current value */ }
[data-part="marker"][data-state="under-value"] { /* marker below current value */ }
[data-part="marker"][data-state="over-value"] { /* marker above current value */ }

Accessibility

Adheres to the Slider WAI-ARIA design pattern.

Keyboard interactions

KeyDescription
ArrowRight / ArrowUpIncrements the focused thumb by the step
ArrowLeft / ArrowDownDecrements the focused thumb by the step
PageUpIncrements by a larger step
PageDownDecrements by a larger step
HomeSets the focused thumb to the minimum value
EndSets the focused thumb to the maximum value

Build docs developers (and LLMs) love