Skip to content

Fields

Range Slider

A two-thumb slider for choosing a range between bounds.

When to use it: Use it for price bands, date spans, or any from–to pair on one scale.

On this page

Example

Price range

$20 – $60

Range Slider.tsxtsx
import { useState } from "react";
import { RangeSlider, RangeSliderThumb, RangeSliderTrack } from "@comp0/react";

export function Example() {
  const [value, setValue] = useState<[number, number]>([20, 60]);

  return (
    <div className="flex w-full max-w-xs flex-col gap-2">
      <p className="text-base font-medium text-zinc-900 sm:text-sm dark:text-zinc-100">
        Price range
      </p>
      <RangeSlider
        aria-label="Price range"
        className="relative h-6 w-full"
        name="price"
        value={value}
        onChange={setValue}
      >
        <RangeSliderTrack className="absolute top-1/2 h-1.5 w-full -translate-y-1/2 rounded-full bg-zinc-200 dark:bg-zinc-700">
          <div
            className="absolute inset-y-0 rounded-full bg-teal-600 dark:bg-teal-400"
            style={{
              left: "calc(var(--comp0-range-slider-start) * 100%)",
              width:
                "calc((var(--comp0-range-slider-end) - var(--comp0-range-slider-start)) * 100%)",
            }}
          />
        </RangeSliderTrack>
        <RangeSliderThumb
          aria-label="Minimum price"
          className="absolute top-1/2 size-4 -translate-x-1/2 -translate-y-1/2 rounded-full border border-zinc-950/10 bg-white shadow outline-teal-600 focus-visible:outline-2 data-dragging:border-teal-600 dark:border-white/20 dark:bg-zinc-100 dark:outline-teal-400 dark:data-dragging:border-teal-400"
          style={{ left: "calc(var(--comp0-range-slider-start) * 100%)" }}
          thumb="start"
        />
        <RangeSliderThumb
          aria-label="Maximum price"
          className="absolute top-1/2 size-4 -translate-x-1/2 -translate-y-1/2 rounded-full border border-zinc-950/10 bg-white shadow outline-teal-600 focus-visible:outline-2 data-dragging:border-teal-600 dark:border-white/20 dark:bg-zinc-100 dark:outline-teal-400 dark:data-dragging:border-teal-400"
          style={{ left: "calc(var(--comp0-range-slider-end) * 100%)" }}
          thumb="end"
        />
      </RangeSlider>
      <p className="text-base text-zinc-600 tabular-nums sm:text-sm dark:text-zinc-400">
        ${value[0]} – ${value[1]}
      </p>
    </div>
  );
}

Anatomy

Dashed frames are invisible state providers; shaded shapes own real DOM. Numbered pins match the list below.

A wireframe sketch of the assembled component. Each numbered marker matches a part in the list that follows.

  1. RangeSlider

    Group that owns the [start, end] pair and exposes the thumb positions as --comp0-range-slider-start/--comp0-range-slider-end 0..1 fractions for styling. Owns a DOM element.

  2. RangeSliderTrack

    The rail the thumbs travel along; pressing it moves and focuses the nearest thumb. Owns a DOM element.

  3. RangeSliderThumb

    Focusable slider for one end of the range; its announced bounds interlock with the sibling so the thumbs never cross. Owns a DOM element.

Step by step

  1. 1

    Add the main part

    Wrap a RangeSliderTrack and two RangeSliderThumbs (thumb="start" and thumb="end") in RangeSlider with an aria-label.

  2. 2

    Add the supporting parts

    Give each thumb its own aria-label; the thumbs clamp at each other so the range cannot cross.

  3. 3

    Make the behavior clear

    Position the parts with the --comp0-range-slider-start and --comp0-range-slider-end fractions; pass name to submit both ends.

    Exampletsx
    <RangeSlider name="price" defaultValue={[20, 60]} aria-label="Price range"><RangeSliderTrack /><RangeSliderThumb thumb="start" aria-label="Minimum price" /><RangeSliderThumb thumb="end" aria-label="Maximum price" /></RangeSlider>

Keyboard

Moves between the two thumbs.
Increases the focused thumb by one step.
Increases the focused thumb by one step.
Decreases the focused thumb by one step.
Decreases the focused thumb by one step.
PgUp
Increases the focused thumb by ten steps.
PgDn
Decreases the focused thumb by ten steps.
Home
Moves the thumb to its own minimum: min for start, the start value for end.
End
Moves the thumb to its own maximum: the end value for start, max for end.

Forms and accessibility

Submits two hidden inputs, `${name}-start` and `${name}-end`, carrying the pair.

Accessibility checklist

  • Name the group and both thumbs: an aria-label on RangeSlider and one per RangeSliderThumb.
  • Each thumb announces interlocked bounds, so screen readers hear how far it can move right now.
  • Show the selected values as visible text; the colored track alone is not enough.

API reference

Importtsx
import { RangeSlider, RangeSliderThumb, RangeSliderTrack } from "@comp0/react";

RangeSlider

DOM element

Group that owns the [start, end] pair and exposes the thumb positions as --comp0-range-slider-start/--comp0-range-slider-end 0..1 fractions for styling.

PropTypeDescription
value[number, number]Controlled [start, end] pair.
defaultValue[number, number]Initial [start, end] pair.
onChange(value: [number, number]) => voidReceives the next [start, end] pair.
min / max / stepnumberRange bounds and step grid; default 0, 100, and 1.
namestringSubmits two hidden inputs named `${name}-start` and `${name}-end`.
formstringAssociates both hidden values with a form by id.
orientation"horizontal" | "vertical"Announced direction; defaults to "horizontal".
disabledbooleanDisables both thumbs and the track.
aria-labelstringNames the group for assistive technology; required.

RangeSliderTrack

DOM element

The rail the thumbs travel along; pressing it moves and focuses the nearest thumb.

PropTypeDescription
classNamestringStyle the rail; position it relative to the root.

RangeSliderThumb

DOM element

Focusable slider for one end of the range; its announced bounds interlock with the sibling so the thumbs never cross.

PropTypeDescription
thumb"start" | "end"Which end of the range this thumb controls.
aria-labelstringNames this thumb, such as "Minimum price"; required per thumb.

Style hooks

Attributes that appear while a state is true. Target them with Tailwind data variants such as data-open:bg-zinc-100, or with any CSS selector.

[data-dragging]

on RangeSliderThumb

The thumb is being dragged with a captured pointer.
[data-disabled]

on RangeSlider, RangeSliderThumb

The range slider is disabled.
[data-orientation]

on RangeSlider, RangeSliderTrack

The travel direction, horizontal or vertical.
:focus-visible

on RangeSliderThumb

The thumb has keyboard focus.

Keep exploring