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.
<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.
<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.
<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.
<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(default0) — lowest value - max —
number(default100) — highest value - step —
number(default1) — increment values snap to - largeStep —
number(default10) — Page Up/Down and Shift+Arrow increment - minStepsBetweenValues —
number(default0) — minimum gap between range thumbs - onValueChange —
(value, eventDetails) => void, fired while dragging - onValueCommitted —
(value, eventDetails) => void, fired when the change settles - format —
Intl.NumberFormatOptionsapplied toSlider.Value - disabled —
boolean(defaultfalse) — ignore user interaction - orientation —
horizontal·vertical(defaulthorizontal) - name —
string— form field name for the hidden inputs
Slider.Value
- children —
(formattedValues, values) => ReactNodeto render the reading yourself
Slider.Thumb
- getAriaLabel —
(index) => stringto name each thumb (useful on a range) - getAriaValueText —
(formatted, value, index) => stringfor 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
NumberFieldwhen users need to type an exact figure. - Always show the current value so the position isn’t guesswork.
- Set
step(andlargeStep) 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.