Slider

Lets the reader choose a value, or a range of values, by dragging a thumb along a track or with the arrow keys. It comes in three sizes, and the track fills from the start of the line, so it follows the page's reading direction.

Installation

bash
npx @smicolon/fasla-ui@latest add slider

Run init once per project before adding components. See Installation.

Preview

Sizes

The three sizes change only the track: sm is 6px tall, md 8px and lg 12px. The thumb is 16px at every size, so the slider is always 16px tall. It fills the width of its container.

Small
Medium
Large

Value

The filled part of the track shows the value: it runs from the start of the track to the thumb. At the minimum the thumb sits at the start of the track and at the maximum at its end, never past either edge. Set the range with min and max, and how far one step moves with step.

Minimum
Middle
Maximum

States

Focus is a real state, not a prop: press Tab to reach a thumb. Keyboard focus draws a 4px ring around the thumb in place of its shadow. Pressing or dragging with the pointer draws no ring.

Default
Focus

Range

Pass an array to value or defaultValue, such as [25, 75], and the slider gets a thumb for each value, with the track filled between them. onValueChange then hands back an array. A thumb dragged past the other pushes it along, and minStepsBetweenValues keeps the two apart.

Labels and values

The slider shows no text of its own. Put a visible label above it and link the two with aria-labelledby, and show the current value beside the label, so the reader never has to judge it from the track.

Volume40%
PriceSAR 200 to SAR 800

Accessibility

  • Each thumb holds a real range input with the slider role, so screen readers announce its name, its value, and the minimum and maximum. With name, hidden inputs submit the value with its form.
  • Tab moves to each thumb in turn. The arrow keys move a thumb by one step, Page Up and Page Down by ten steps (largeStep), and Home and End move it to the minimum and the maximum.
  • Always name the slider: aria-labelledby with a visible label, or aria-label when there is none. In a range, give each thumb its own name with getAriaLabel, such as Minimum price and Maximum price.
  • Screen readers hear the number alone. When it needs a unit, such as a currency, return the full text from getAriaValueText.
  • Direction is inherited from dir, never a prop. On a right-to-left page the track fills from the right, the thumb starts at the right end, and the left arrow raises the value, so every arrow moves the thumb the way it points.

Props

Slider

PropTypeDefaultDescription
valuenumber | number[]—The current value: a number, or an array for a range. Use it with onValueChange.
defaultValuenumber | number[]—The value on first render. An array gives a thumb for each value.
onValueChange(value) => void—Called with the new value while a thumb moves.
onValueCommitted(value) => void—Called once with the final value, when the pointer is released or a key changes it.
size"sm" | "md" | "lg""md"Track height: 6, 8 or 12px. The thumb is 16px at every size.
minnumber0The lowest value.
maxnumber100The highest value.
stepnumber1How far one step moves the value. The value snaps to multiples of it.
largeStepnumber10How far Page Up and Page Down move the value.
minStepsBetweenValuesnumber0In a range, the fewest steps allowed between the two thumbs.
aria-labelstring—Names the slider for screen readers when there is no visible label.
aria-labelledbystring—The id of the visible label that names the slider.
getAriaLabel(index) => string—Names each thumb of a range by its index: 0 for the start, 1 for the end.
getAriaValueText(text, value, index) => string—Returns the text screen readers hear for a thumb's value.
namestring—The name the value is submitted under.
formstring—The id of the form the value is submitted with, when the slider sits outside it.
classNamestring—Extra classes for the slider.

Usage

Basic slider

tsx
import { Slider } from "@/components/ui/slider"

export function VolumeSlider() {
  return <Slider name="volume" defaultValue={60} aria-label="Volume" />
}

Price range, controlled

tsx
"use client"

import * as React from "react"
import { Slider } from "@/components/ui/slider"

export function PriceFilter() {
  const [price, setPrice] = React.useState<readonly number[]>([200, 800])
  return (
    <Slider
      value={price}
      onValueChange={setPrice}
      min={0}
      max={1000}
      step={50}
      getAriaLabel={(index) => (index === 0 ? "Minimum price" : "Maximum price")}
    />
  )
}

With a visible label and value

tsx
"use client"

import * as React from "react"
import { Slider } from "@/components/ui/slider"

export function VolumeField() {
  const id = React.useId()
  const [volume, setVolume] = React.useState(40)
  return (
    <div className="flex flex-col gap-3">
      <div className="flex items-baseline justify-between gap-4">
        <span id={id} className="text-sm font-medium">
          Volume
        </span>
        <span className="text-sm tabular-nums text-muted-foreground">{volume}%</span>
      </div>
      <Slider value={volume} onValueChange={setVolume} aria-labelledby={id} />
    </div>
  )
}