A control for choosing a number, or a range between two numbers, along a track by dragging a thumb or pressing the arrow keys, such as how many minutes of movement the map draws behind each vehicle, with its label, its line of help and its error from Field.
When to use
Use Slider for a number, or a range between two numbers, where the place in a range matters more than the exact figure, such as a trail's length or a walking distance.
| Use instead | When |
|---|---|
| NumberField | The person knows the exact number, such as how many departures a board shows |
| Meter | The value shows where it sits in a range, and the person can't change it |
Kongens Nytorv
Metro M1, M2 and M3.
Minutes of movement drawn behind each vehicle
Variants and states
| Rest | Disabled | |
|---|---|---|
| Default | Minutes of movement drawn behind each vehicle | Minutes of movement drawn behind each vehicle |
- Rest
Minutes of movement drawn behind each vehicle
- Disabled
Minutes of movement drawn behind each vehicle
Slider styles hover, pressed and focus on its inner parts, which a still preview can't reach, so this table shows rest and disabled only.
Props
| Name | Type | Default | Description |
|---|---|---|---|
label* | ReactNode | — | Required. What the number is, in sentence case, such as "Trail length": shown above the slider, and the name of the slider's group and of a single thumb; a range's thumbs are named "Minimum" and "Maximum". |
hiddenLabel | boolean | false | Whether the label is heard but not seen. Use it only where the screen around the slider already says what it is for. |
description | ReactNode | — | A line of help between the label and the slider, such as "Minutes of movement drawn behind each vehicle", read after the label by a screen reader. Left out, no description is rendered. |
error | ReactNode | — | What is wrong with the value and how to put it right, such as "Choose 8 minutes or fewer". Given, it marks the slider invalid and is shown under it; left out, the slider shows Base UI's own message when its checks fail. |
disabled | boolean | false | Whether the slider ignores the person: no thumb takes focus, a press or a key changes nothing, and the label, the help, the fill and the figure turn to the tertiary ink. |
value | number | readonly [ number, number ] | — | The value, when the caller controls it: a number for one thumb, or a pair for a range, its two thumbs at the lower and the upper end. |
defaultValue | number | readonly [ number, number ] | — | The value at first, when the slider keeps it itself: a number, or a pair for a range. Left out, a single thumb starts at min. |
onChange | (event: Event, value: number | readonly [number, number], details: Slider.Root.ChangeEventDetails) => void | — | Called at every step the value moves, with the new value: by a key (details.reason 'keyboard'), a drag ('drag'), a press on the track ('track-press'), or a screen reader or the browser setting the hidden range input itself ('input-change'); details.cancel() keeps the value as it was. event is details.event, whose target holds value and name, for a form library. |
onChangeCommitted | (event: Event, value: number | readonly [number, number], details: Slider.Root.CommitEventDetails) => void | — | Called once the person settles on a value: when a drag ends (details.reason 'drag'), the track is pressed ('track-press'), a key is pressed ('keyboard'), or a screen reader or the browser sets the hidden range input itself ('input-change'). It is not called when the value didn't change. Use it to act once on the final value, such as to fetch, rather than at every step. event is details.event. |
locale | LocalesArgument | The PerronProvider's locale | The locale Intl.NumberFormat formats the figure and each thumb's value text in: "1.5 km" in English, "1,5 km" in Danish, Swedish and Norwegian. Left out, the nearest PerronProvider's. |
minimumLabel | string | The locale's | Overrides "Minimum", the name of a range's lower thumb. |
maximumLabel | string | The locale's | Overrides "Maximum", the name of a range's upper thumb. |
ref | Ref<HTMLDivElement> | — | Reaches the root, the Slider.Root <div>. |
slotProps | { field?: Omit<FieldRootProps, "children" | "disabled" | "name"> | undefined; label?: FieldLabelProps | undefined; description?: FieldDescriptionProps | undefined; error?: FieldErrorProps | undefined; control?: SliderControlProps | undefined; track?: SliderTrackProps | undefined; indicator?: SliderIndicatorProps | undefined; thumb?: Omit<SliderThumbProps, "index"> | undefined; value?: Omit<SliderValueProps, "children"> | undefined; } | — | Props for each part: field, label, description, error, control, track, indicator, thumb, value. |
label (required)ReactNode, default —- Required. What the number is, in sentence case, such as "Trail length": shown above the slider, and the name of the slider's group and of a single thumb; a range's thumbs are named "Minimum" and "Maximum".
hiddenLabelboolean, defaultfalse- Whether the label is heard but not seen. Use it only where the screen around the slider already says what it is for.
descriptionReactNode, default —- A line of help between the label and the slider, such as "Minutes of movement drawn behind each vehicle", read after the label by a screen reader. Left out, no description is rendered.
errorReactNode, default —- What is wrong with the value and how to put it right, such as "Choose 8 minutes or fewer". Given, it marks the slider invalid and is shown under it; left out, the slider shows Base UI's own message when its checks fail.
disabledboolean, defaultfalse- Whether the slider ignores the person: no thumb takes focus, a press or a key changes nothing, and the label, the help, the fill and the figure turn to the tertiary ink.
valuenumber | readonly [ number, number ], default —- The value, when the caller controls it: a number for one thumb, or a pair for a range, its two thumbs at the lower and the upper end.
defaultValuenumber | readonly [ number, number ], default —- The value at first, when the slider keeps it itself: a number, or a pair for a range. Left out, a single thumb starts at
min. onChange(event: Event, value: number | readonly [number, number], details: Slider.Root.ChangeEventDetails) => void, default —- Called at every step the value moves, with the new value: by a key (
details.reason'keyboard'), a drag ('drag'), a press on the track ('track-press'), or a screen reader or the browser setting the hidden range input itself ('input-change');details.cancel()keeps the value as it was.eventisdetails.event, whosetargetholdsvalueandname, for a form library. onChangeCommitted(event: Event, value: number | readonly [number, number], details: Slider.Root.CommitEventDetails) => void, default —- Called once the person settles on a value: when a drag ends (
details.reason'drag'), the track is pressed ('track-press'), a key is pressed ('keyboard'), or a screen reader or the browser sets the hidden range input itself ('input-change'). It is not called when the value didn't change. Use it to act once on the final value, such as to fetch, rather than at every step.eventisdetails.event. localeLocalesArgument, default The PerronProvider's locale- The locale
Intl.NumberFormatformats the figure and each thumb's value text in: "1.5 km" in English, "1,5 km" in Danish, Swedish and Norwegian. Left out, the nearest PerronProvider's. minimumLabelstring, default The locale's- Overrides "Minimum", the name of a range's lower thumb.
maximumLabelstring, default The locale's- Overrides "Maximum", the name of a range's upper thumb.
refRef<HTMLDivElement>, default —- Reaches the root, the
Slider.Root<div>. slotProps{ field?: Omit<FieldRootProps, "children" | "disabled" | "name"> | undefined; label?: FieldLabelProps | undefined; description?: FieldDescriptionProps | undefined; error?: FieldErrorProps | undefined; control?: SliderControlProps | undefined; track?: SliderTrackProps | undefined; indicator?: SliderIndicatorProps | undefined; thumb?: Omit<SliderThumbProps, "index"> | undefined; value?: Omit<SliderValueProps, "children"> | undefined; }, default —- Props for each part:
field,label,description,error,control,track,indicator,thumb,value.
Use it
import { Slider } from '@mylder/perron'Ask your agent
get_reference({ library: "base-ui", component: "Slider" })get_reference({ library: "base-ui", component: "Field" })Keyboard and ARIA
From the Base UI reference for Slider and the installed SliderRoot, SliderControl, SliderThumb and SliderValue, and Field's spec for the frame. Slider adds no keys or pointer handling of its own.
| Key or press | Does | From |
|---|---|---|
Tab, Shift+Tab | Move focus to and from each thumb's input in turn: one tab stop per thumb, the lower thumb first in a range; the label, the figure and the track take none | Base UI |
ArrowRight, ArrowUp | Raise the focused thumb's value by step; ArrowRight lowers it only inside Base UI's DirectionProvider set to rtl, which PerronProvider doesn't render | Base UI |
ArrowLeft, ArrowDown | Lower the focused thumb's value by step; ArrowLeft raises it only inside Base UI's DirectionProvider set to rtl, which PerronProvider doesn't render | Base UI |
Shift with an arrow key | Move the value by largeStep in the arrow's direction | Base UI |
PageUp, PageDown | Raise or lower the value by largeStep | Base UI |
Home, End | Move the value to min or max; in a range, the upper thumb's Home and the lower thumb's End stop minStepsBetweenValues steps short of the other thumb | Base UI |
| A press or drag on the control | Moves the nearest thumb to the pointer, focuses it without the ring, and follows the drag until release | Base UI |
| A press on the label | Focuses a single thumb's input, natively through htmlFor; in a range the label's htmlFor names no input, and a press focuses nothing | Base UI |
| Element | Role | Accessible name from | States announced |
|---|---|---|---|
root | group | aria-labelledby the field's label: "Trail length" | aria-describedby the description and the mounted error; aria-invalid while invalid and not disabled |
| A thumb's input | slider, a native <input type="range"> | A single thumb: aria-labelledby the field's label, and the native label through htmlFor, "Trail length". A range: aria-label the owned "Minimum" and "Maximum" | aria-valuenow, the native min and max, aria-valuetext (the formatted value when format is given, and always in a range), aria-orientation="horizontal", aria-describedby, aria-invalid, disabled |
value | status, a native <output> with aria-live="off" | none | None live: its text, "5", is read in place, and never announced as it changes |
valueSizer | none: aria-hidden | none | none |
label, description, error | Field's | Field's | Field's |
The figure stays an <output> with Base UI's aria-live="off": the value is heard from the focused thumb's aria-valuenow or aria-valuetext at every step, so a live figure would say each step a second time, and Base UI keeps it off for that reason. Read in place, it repeats the value the thumb's input says, which a screen reader's reading of the page expects of a control's visible figure.
What axe won't catch, from the contract, each with how this component meets it:
- Everything the eye is told, the ear is told, for every shape each prop accepts: the thumb's place and the figure are the input's
aria-valuenow, or itsaria-valuetextin the caller'sformat; a range's two thumbs are named "Minimum" and "Maximum" and each says its own value, never Base UI's English "start range" (CR-7); the description and the error are each thumb'saria-describedby; an invalid slider isaria-invalidand its error is words, never only the thumb's red edge; the sizers, which the eye never sees, arearia-hidden. - Every landmark and named region has an accessible name: the root's
groupis named by the field's label, even hidden; Slider is not a landmark. - Every default label can be overridden:
minimumLabelandmaximumLabeloverride "Minimum" and "Maximum", andslotProps.thumb.getAriaLabelnames the thumbs in the caller's own words (see Owned strings). - Every shape the type accepts renders, in every variant:
label,descriptionanderrorrender a string, an element and a list alike, by Field;valueanddefaultValuerender one thumb for a number and two for a pair; a long Danish compound in the label or the description wraps, by Field's CR-8, and the figure keeps one line. - A hidden copy kept for animation is
aria-hidden, and a collapsed region isinert: the two sizers, copies of the end figures kept for the figure's width, arearia-hiddenandvisibility: hidden; nothing collapses. - Native semantics first: Base UI's native
<input type="range">per thumb, its<output>for the figure, agroupround them and Field's native<label>; Perron adds no ARIA beyond three attributes: a range thumb'saria-labelthrough Base UI'sgetAriaLabeland itsaria-valuetextthroughgetAriaValueText(CR-7), andaria-hiddenon the two sizers (CR-8). - Focus moves in reading order and returns on close: each thumb is one tab stop, the lower thumb first, between the label above and the error below; nothing opens or closes.
- Every target is at least
size.target-minsquare: the control issize.target-mintall across the first column, and a press anywhere on it moves the nearest thumb; the thumb, 18 px, sits inside it. - Loops stop when the system asks for less motion: none here; nothing loops and nothing transitions.
Owned strings
In a range, the two thumbs are named by new owned strings, minimum "Minimum" and maximum "Maximum", given to Base UI's Slider.Thumb through getAriaLabel and overridden by minimumLabel and maximumLabel; and each range thumb's aria-valuetext is its formatted value alone, through getAriaValueText, in place of Base UI's default, which appends the English words "start range" and "end range". Base UI's guidance names each thumb of a range with its own aria-label beside the group's visible label, and the ear hears "Departures per hour, group" then "Minimum, slider, 4"; "Minimum" and "Maximum" fit any quantity, where "From" and "To" fit a time but not a count; Base UI's "start range" is English in every locale, which the owned-strings check would fail in Danish; and a single thumb needs no owned word, since the field's label names it.
| Key | English | Override | Seen or heard where |
|---|---|---|---|
minimum | "Minimum" | minimumLabel | Heard as the name of a range's lower thumb, as "Minimum, slider, 4"; never shown |
maximum | "Maximum" | maximumLabel | Heard as the name of a range's upper thumb, as "Maximum, slider, 12"; never shown |
Both keys are new, described in en.ts as A range slider: the name of its lower thumb, heard as "Minimum, slider, 4". and A range slider: the name of its upper thumb, heard as "Maximum, slider, 12"., with the Danish, Swedish and Norwegian written by the build.
Every other word is the caller's, through label, description and error, or comes from Intl.NumberFormat in the slider's locale and the caller's format, such as the unit in "1.5 km", or is Base UI's message for a failing check, as Field's spec says.
Do and don't
For a caller using the component, with the reason. Each row is a rule: a must row's Don't is never right, a should row's needs a reason, and Checked by says what catches the Don't in a caller's code.
| Rule | Level | Do | Don't | Checked by |
|---|---|---|---|---|
| DD-1 | should | Use a slider for a number where the place in a range matters more than the exact figure, such as a trail's length or a walking distance | Use a slider for an exact number the person knows, such as a stop number or a year: a number field takes it in one step, where a slider takes many key presses | review |
| DD-2 | should | Give a label that names the quantity, "Trail length", and put its unit in format, so the figure says "1.5 km" | Write the unit into the label alone ("Walking distance in km"): the figure and the ear then say a bare number | review |
| DD-3 | should | Keep the range short enough that each step is a place the thumb can be put by hand: a few dozen steps across the field | Set a step so fine that a drag skips values the person wants; give a largeStep for long ranges, so Page Up and Shift with an arrow cross them | review |
| DD-4 | should | Act on onChangeCommitted when a change starts work, such as a fetch or a redraw of the map | Fetch on every onChange: a drag calls it at every step | review |
| DD-5 | must | Give a range as a pair, [4, 12], and let Slider name its thumbs "Minimum" and "Maximum", or give minimumLabel and maximumLabel | Name a range's thumbs with aria-label on slotProps.thumb, which gives both thumbs the same name; use getAriaLabel there, which takes each thumb's index | review |
| DD-6 | must | Word an error as what to do, "Choose 8 minutes or fewer" | Blame the person or say only that it failed ("Invalid value"), against Content design | review |
| DD-7 | must | Put a slider in a bottom Sheet as it is: it keeps a drag along it from swiping the sheet, and a touch drag across it still swipes (CR-11) | Wrap it in another element with data-base-ui-swipe-ignore: with no axis, the wrapper drops the sheet's swipe for every touch on the slider, so a drag across it no longer swipes the sheet; nor remove the control's own, which lets a drag along the slider move the sheet | review |
| DD-8 | must | Use a Meter to show a value the person can't change | Use a disabled slider as a meter: it reads as a control that is switched off | review |