Slider

A value or a range picked by dragging along a track, with marks, steps, a tooltip and a vertical orientation.

Accessibility

Slider is built from divs. The keyboard works, but the handles have no name until you give them one, and a restyle can easily break the handle's target size and the track's contrast.

What it renders

A Slider has no <input>. The rail, track, marks and dots are plain divs, and screen readers only see the handles.
  • Each handle has role="slider" with aria-valuemin, aria-valuemax and aria-valuenow, updated on every change.
  • aria-orientation follows vertical, so you never set it.
  • A disabled handle gets aria-disabled and leaves the tab order.
  • A range is two separate sliders to a screen reader, with no group around them, so name each handle.
  • There is no default name. An unlabeled slider is read as “slider” and a number.

Keyboard

Each handle can be focused and moved with the keyboard. Know what each key does before you choose a step.
  • Tab reaches each handle: one stop for a single slider, two for a range. tabIndex takes an array to change the order.
  • Arrow keys move one step. Up adds and Down subtracts, and Left and Right follow the text direction, so a right-to-left or reversed slider still moves the right way.
  • Page Up and Page Down move two steps. On 0 to 100 with a step of 1, that is barely faster than an arrow. For bigger jumps, use a coarser step, or marks with step={null}.
  • Home and End go to the minimum and the maximum.
  • Backspace and Delete remove the focused handle in an editable range, with no confirmation. Set minCount so the reader cannot delete every handle.
  • onChange fires on every key press. onChangeComplete fires once, on key up, which is where anything slow belongs.
  • keyboard={false} turns the keys off but leaves the handle focusable, which is worse than disabled. Disable the slider instead.

Naming the slider

A handle has no text, and a <label> cannot point at it. Name it with a prop.
  • ariaLabelForHandle takes a string, or an array with one entry per handle. In a range, “Minimum price” and “Maximum price” tell the reader which handle they are on.
  • ariaLabelledByForHandle points at the id of text already on the screen. Prefer it when that text exists. It takes the same array.
  • A caption next to the slider does not name it by itself. Give the caption an id and point ariaLabelledByForHandle at it.
  • ariaRequired sets aria-required on each handle. Use it only if the form really refuses to submit without a value.

When the number is not the meaning

By default a handle is read as its number, like “42”. When the number is not what the value means, give the reader the words.
  • ariaValueTextFormatterForHandle sets aria-valuetext, the phrase that is read out. It takes the same per-handle array as the labels.
  • The tooltip formatter does not change it. A tooltip showing “42%” can sit over a handle read as “42”. Set both, to the same words.
  • Use it when the scale has a unit (“42 percent”), when an end is special (“Unlimited”), or when values map to words (“Medium”), as on a slider with marks and step={null}.

Target size and contrast

  • The handle is small, about 14px to grab, well under the 24px minimum target size. Raise handleSize in the theme when the slider is a main touch control.
  • The rail is only 4px tall. Keep a touch slider away from the edge of a scrolling panel, where a missed grab becomes a scroll.
  • The filled track needs 3:1 contrast against the rail, since it is what shows the selected part. A custom colorPrimary can push it below that without any warning.
  • Do not let the handle's position be the only readout. Show the value with a mark, a pinned tooltip or an InputNumber beside it.

Motion and announcements

  • The handle moves with a short transition, and a drag is the reader's own motion. Put anything you add behind motion-safe:.
  • The tooltip also opens on focus, so keyboard users see the value. Do not pin every tooltip open in a range to make up for it, since pinned tooltips cover the marks.
  • Do not announce from onChange, which fires on every step and reads out every value on the way. Announce from onChangeComplete, if aria-valuetext is not enough.
  • A disabled slider cannot be focused, so its value cannot be reached. Show it as text nearby.