Photon Design System

Slider

Picks a number — or a range — along a track by dragging. Built on Base UI Slider, with keyboard support and aria-valuenow. For a precise typed value, prefer NumberField.

Import

import { Slider } from "@photon-ai/pho-ui/components/slider";

Basic

Set defaultValue (or value), plus min / max / step on Slider.Root. Compose a header with Slider.Label + Slider.Value, then the track.

Volume
40
<Slider.Root defaultValue={40}>
  <div className="flex items-center justify-between">
    <Slider.Label>Volume</Slider.Label>
    <Slider.Value />
  </div>
  <Slider.Control>
    <Slider.Track>
      <Slider.Indicator />
      <Slider.Thumb />
    </Slider.Track>
  </Slider.Control>
</Slider.Root>

Range

Pass an array defaultValue and render one Slider.Thumb per value for a two-handle range.

Price range
25 – 75
<Slider.Root defaultValue={[25, 75]}>
  …
  <Slider.Track>
    <Slider.Indicator />
    <Slider.Thumb />
    <Slider.Thumb />
  </Slider.Track>
</Slider.Root>

Steps and formatting

step snaps the value to fixed increments (here $50), and format takes Intl.NumberFormatOptions so Slider.Value reads out in the right units.

Monthly budget
$400
<Slider.Root
  defaultValue={400}
  min={0}
  max={1000}
  step={50}
  format={{ style: "currency", currency: "USD", maximumFractionDigits: 0 }}
>
  <div className="flex items-center justify-between">
    <Slider.Label>Monthly budget</Slider.Label>
    <Slider.Value />
  </div>
  …
</Slider.Root>

Disabled

Set disabled on Slider.Root to freeze the value and drop the thumb out of the tab order.

Opacity
60
<Slider.Root defaultValue={60} disabled>
  …
</Slider.Root>

Anatomy

  • Slider.Root — owns value, min, max, step, orientation
  • Slider.Label / .Value — name and current reading
  • Slider.Control → Track → Indicator (fill) + Thumb (handle)

Props

Slider.Root

  • value / defaultValue — number | number[] — controlled / uncontrolled; an array for a range
  • min — number (default 0) — lowest value
  • max — number (default 100) — highest value
  • step — number (default 1) — increment values snap to
  • largeStep — number (default 10) — Page Up/Down and Shift+Arrow increment
  • minStepsBetweenValues — number (default 0) — minimum gap between range thumbs
  • onValueChange — (value, eventDetails) => void, fired while dragging
  • onValueCommitted — (value, eventDetails) => void, fired when the change settles
  • format — Intl.NumberFormatOptions applied to Slider.Value
  • disabled — boolean (default false) — ignore user interaction
  • orientation — horizontal · vertical (default horizontal)
  • name — string — form field name for the hidden inputs

Slider.Value

  • children — (formattedValues, values) => ReactNode to render the reading yourself

Slider.Thumb

  • getAriaLabel — (index) => string to name each thumb (useful on a range)
  • getAriaValueText — (formatted, value, index) => string for a spoken value
  • index — the thumb’s slot in the value array (needed for SSR ranges)

Accessibility

Each Slider.Thumb renders a <div> around a native <input type="range"> with role="slider", aria-valuenow, aria-valuemin, aria-valuemax, and aria-orientation. Name the control with Slider.Label (associated for you), or aria-label / getAriaLabel(index) per thumb on a range. Use getAriaValueText when the raw number needs units or words to be understood (“$400”, not “400”).

Keyboard, with a thumb focused:

  • Arrow Right / Up — increase by step
  • Arrow Left / Down — decrease by step
  • Page Up / Page Down (or Shift + Arrow) — move by largeStep
  • Home / End — jump to min / max

Each thumb’s input is individually tabbable and shows a ring on focus-visible; a disabled slider drops out of the tab order and dims to data-disabled. Slider.Value renders an <output> mirroring the live reading for sighted users, while the same number reaches assistive tech through aria-valuenow.

Best practices

  • Use a slider for an approximate value where dragging feels natural (volume, opacity); use NumberField when users need to type an exact figure.
  • Always show the current value so the position isn’t guesswork.
  • Set step (and largeStep) to the precision users actually need — a $50 step beats free-scrubbing pixels.
  • For a range, keep both thumbs on one track and label what the span means.