Slider

A range input with draggable thumbs. Supports single value, ranges (two thumbs), custom steps, and full keyboard control.

Volume33%
Price range$20 – $80

Stepped (step = 10)

Disabled

Installation

pnpm
pnpm dlx @hex-core/cli add slider

Range slider

Two-thumb range

tsx
<Slider defaultValue={[20, 80]} max={100} step={1} />

Controlled price range

Two-thumb range bound to React state — the canonical price-range or date-range filter. The value prop is always a number[], even for single-thumb usage.

tsx
import { useState } from "react";

const formatUSD = (amount: number) =>
  new Intl.NumberFormat("en-US", { style: "currency", currency: "USD", maximumFractionDigits: 0 }).format(amount);

export function PriceRangeFilter() {
  const [range, setRange] = useState<[number, number]>([100, 750]);
  const [min, max] = range;

  return (
    <div className="grid w-72 gap-3">
      <div className="flex items-center justify-between">
        <Label htmlFor="price" className="text-sm">Price range</Label>
        <span className="text-sm tabular-nums text-muted-foreground">
          {formatUSD(min)}{formatUSD(max)}
        </span>
      </div>
      <Slider
        id="price"
        min={0}
        max={1000}
        step={25}
        value={range}
        onValueChange={(next) => setRange(next as [number, number])}
        aria-label="Price range"
        thumbLabels={["Minimum price", "Maximum price"]}
      />
    </div>
  );
}

API Reference

PropTypeDefaultDescription
value
objectControlled array of thumb values (number[]), e.g. [50] for single, [20, 80] for range
defaultValue
objectDefault array of thumb values (number[]) for uncontrolled usage
onValueChange
functionCallback on value change: (value: number[]) => void
min
number0Minimum value
max
number100Maximum value
step
number1Step interval between valid values
disabled
booleanfalseDisable the slider
orientation
"horizontal" | "vertical"horizontalSlider direction
aria-label
stringAccessible label for the slider as a whole. Mirrored onto a single thumb automatically; for range sliders prefer thumbLabels.
aria-labelledby
stringId of an external visible label that names the slider.
thumbLabels
objectPer-thumb accessible labels (string[]). Required for range sliders so each thumb has a meaningful name (e.g. ['Minimum price', 'Maximum price']). For a single-thumb slider, the Root's aria-label is mirrored onto the thumb automatically and thumbLabels is only needed when overriding that default.

AI Guidance

When to use

Use for continuous numeric inputs with a known range: volume, brightness, price range filter, opacity. Pair value with a visible number display when the exact value matters.

When not to use

Don't use when the user needs to enter an exact number (use Input type=number). Don't use for discrete choices (use Select or RadioGroup).

Common mistakes

  • Using Slider for exact values without showing the number
  • Missing min/max bounds
  • Using step=1 for fractional values (set step=0.01)
  • Not providing aria-label / aria-labelledby when there's no visible label
  • Range slider with only Root aria-label and no thumbLabels — both thumbs fall back to '(N of 2)' indexed names instead of meaningful per-thumb labels
  • thumbLabels.length !== value.length — extra labels are ignored, missing labels fall back to indexed names (dev-mode warning)

Accessibility

Arrow keys step by step, Home/End jump to min/max, PageUp/PageDown step larger. Radix handles aria-valuemin/max/now. Each thumb has its own accessible name: explicit via thumbLabels[i], else mirrored from the Root's aria-label (single thumb) or indexed '(i of N)' fallback (range). Add aria-label / aria-labelledby on the Root when there's no visible label.

Related components

Token budget: 450