Skip to main content
View Zag.js on Github
Join the Discord server

Range Slider

A range slider is a multi-thumb slider used to select a range between two numbers.

10 - 60


  • Fully managed keyboard navigation.
  • Supports touch or click on track to update value.
  • Supports Right-to-Left directionality.
  • Support for horizontal and vertical orientations.
  • Prevents text selection while dragging.


To use the range slider machine in your project, run the following command in your command line:

npm install @zag-js/range-slider @zag-js/react # or yarn add @zag-js/range-slider @zag-js/react

This command will install the framework agnostic range slider logic and the reactive utilities for your framework of choice.


To set up the slider correctly, you'll need to understand its anatomy and how we name its parts.

Each part includes a data-part attribute to help identify them in the DOM.

On a high level, the slider consists of:

  • Root: The root container for the slider
  • Label: The accessible label for the slider
  • Control: The container for the slider's track and thumb
  • Track: The slider's track element
  • Range: The element that visually represents the slider's range
  • Thumb: The control element used to drag the slider
  • Output: The element that displays the current value of the slider


First, import the range slider package into your project

import * as rangeSlider from "@zag-js/range-slider"

The range slider package exports two key functions:

  • machine — The state machine logic for the slider widget as described in the WAI-ARIA spec.
  • connect — The function that translates the machine's state to JSX attributes and event handlers.

You'll need to provide a unique id to the useSetup hook. This is used to ensure that every part has a unique identifier.

Next, import the required hooks and functions for your framework and use the range slider machine in your project 🔥

import * as rangeSlider from "@zag-js/range-slider" import { useMachine, normalizeProps } from "@zag-js/react" export function RangeSlider() { const [state, send] = useMachine( rangeSlider.machine({ id: "1", name: ["min", "max"], values: [10, 60], }), ) const api = rangeSlider.connect(state, send, normalizeProps) return ( <div {...api.rootProps}> <div {...api.trackProps}> <div {...api.rangeProps} /> </div> {, index) => ( <div key={index} {...api.getThumbProps(index)}> <input {...api.getInputProps(index)} /> </div> ))} </div> ) }

Changing the orientation

By default, the slider is assumed to be horizontal. To change the orientation to vertical, set the orientation property in the machine's context to vertical.

In this mode, the slider will use the arrow up and down keys to increment/decrement its value.

Don't forget to change the styles of the vertical slider by specifying its height

const [state, send] = useMachine( rangeSlider.machine({ orientation: "vertical", }), )

Setting the initial value

const [state, send] = useMachine( rangeSlider.machine({ values: [30, 60], }), )

Specifying the minimum and maximum

By default, the minimum is 0 and the maximum is 100. If that's not what you want, you can easily specify different bounds by changing the values of the min and/or max attributes.

For example, to ask the user for a value between -10 and 10, you can use:

const [state, send] = useMachine( rangeSlider.machine({ min: -10, max: 10, }), )

Setting the value's granularity

By default, the granularity, is 1, meaning that the value is always an integer. You can change the step attribute to control the granularity.

For example, If you need a value between 5 and 10, accurate to two decimal places, you should set the value of step to 0.01:

const [state, send] = useMachine( rangeSlider.machine({ min: 5, max: 10, step: 0.01, }), )

Listening for changes

When the slider value changes, the onChange and onChangeEnd callbacks are invoked. You can use this to setup custom behaviors in your app.

const [state, send] = useMachine( rangeSlider.machine({ onChange(values) { console.log("value changing to:", values) }, onChangeEnd(values) { console.log("value has changed to:", values) }, }), )

Preventing thumb overlap

By default, the range slider thumbs are allowed to overlap when their values are equal. To prevent this, use the minStepsBetweenThumbs to avoid thumbs with equal values.

const [state, send] = useMachine( rangeSlider.machine({ minStepsBetweenThumbs: 1, }), )

Usage within forms

To use slider within forms, use the exposed getInputProps from the connect function and ensure you pass name value to the machine's context. It will render a hidden input for each value and ensure the value changes get propagated to the form correctly.

const [state, send] = useMachine( rangeSlider.machine({ name: "quantity", }), )

RTL Support

The slider has built-in support for RTL alignment and interaction. In the RTL mode, operations are performed from right to left, meaning, the left arrow key will increment and the right arrow key will decrement.

To enable RTL support, pass the dir: rtl context property

const [state, send] = useMachine( rangeSlider.machine({ dir: "rtl", }), )

While we take care of the interactions in RTL mode, you'll have to ensure you apply the correct CSS styles to flip the layout.

Methods and Properties

The range slider's api provides properties and methods you can use to programmatically read and set the slider's value.


  • values: The current value of the range slider (as an array)
  • isDragging: Whether a range-slider thumb is currently being dragged
  • isFocused: Whether a range-slider thumb is currently focused (and not dragged)


  • setValue: Used to set the array value of the range slider
  • getThumbValue: Returns the value of the thumb at the specified index
  • setThumbValue: Used to set the value of a thumb at the specified index
  • getThumbPercent
    the percent value of the thumb at the specified index
  • getThumbMin: Returns the minimum value of the thumb at the specified index
  • getThumbMax: Returns the maximum value of the thumb at the specified index
  • increment: Used to increment the value of the thumb at the specified index
  • decrement: Used to decrement the value of the thumb at the specified index
  • focus: Used to focus the thumb at the specified index
  • blur: Used to blur the currently focused thumb

Styling guide

Earlier, we mentioned that each slider part has a data-part attribute added to them to select and style them in the DOM.

Focused State

When the slider thumb is focused, the data-focus attribute is added to the root, control, thumb and label parts.

[data-part="root"][data-focus] { /* styles for root focus state */ } [data-part="thumb"]:focus { /* styles for thumb focus state */ } [data-part="control"][data-focus] { /* styles for control focus state */ } [data-part="track"][data-focus] { /* styles for track focus state */ } [data-part="range"][data-focus] { /* styles for range focus state */ }

Disabled State

When the slider is disabled, the data-disabled attribute is added to the root, label, control and thumb.

[data-part="root"][data-disabled] { /* styles for root disabled state */ } [data-part="label"][data-disabled] { /* styles for label disabled state */ } [data-part="control"][data-disabled] { /* styles for control disabled state */ } [data-part="output"][data-disabled] { /* styles for output disabled state */ } [data-part="thumb"][data-disabled] { /* styles for thumb disabled state */ } [data-part="range"][data-disabled] { /* styles for range disabled state */ }


[data-part="root"][data-orientation="(horizontal|vertical)"] { /* styles for horizontal or vertical */ } [data-part="thumb"][data-orientation="(horizontal|vertical)"] { /* styles for horizontal or vertical */ } [data-part="track"][data-orientation="(horizontal|vertical)"] { /* styles for horizontal or vertical */ }

Edit this page on GitHub

On this page