Forms & Input

Slider

Available

Selects a value from a range. Uses aria-valuenow/min/max with keyboard operation.

What Is a Slider?

A slider is a UI control that lets users select a value from a range by moving a thumb horizontally (or vertically). It is commonly used for volume, brightness, playback position, and similar settings.

The key points are: communicate the current value and the allowed range (min/max) to users who cannot see the screen, and make it operable with a keyboard, not just a mouse.

Why Does Accessibility Matter?

The easiest way to satisfy both requirements is the native<input type="range">. The browser provides keyboard interaction and screen reader announcements out of the box.

Live Demo (Recommended Implementation)

Below is a native <input type="range">.Try operating it without a mouse — focus the thumb and use the arrow keys.

Accessible Slider (native range)
40

Try it: Tab to the thumb → ← → to adjust by 1, Home / End for min/max, Page Up / Page Down for large steps.

Tip

When a screen reader focuses the thumb, it announces something like "Volume, slider, 40, minimum 0, maximum 100" — role, current value, and range all at once. This is provided automatically by the native element.

Keyboard Interaction

KeyActionPriority
/ Increase the value by one stepRequired
/ Decrease the value by one stepRequired
HomeSet to the minimum valueRequired
EndSet to the maximum valueRequired
Page Up / Page DownIncrease or decrease the value by a larger stepRecommended

Note

All the keyboard interactions above come for free when you use<input type="range">. If you build a custom slider withrole="slider", you must implement all of them yourself viakeydown handlers.

Required WAI-ARIA Roles & Properties

TargetAttribute / RoleMeaning
Native range inputAssociate with <label for>Gives the thumb an accessible name (label). This alone provides the role, value, and range automatically.
Custom thumbrole="slider"Conveys to assistive technologies that this is a slider.
Custom thumbtabindex="0"Makes the div element focusable via keyboard.
Custom thumbaria-valuemin / aria-valuemaxDefines the allowable range (minimum and maximum).
Custom thumbaria-valuenowThe current value. Must be updated each time the slider is moved.
Custom thumbaria-valuetextProvides a human-readable representation when the numeric value alone is not meaningful (e.g., "40%", "Medium"). Optional.
Custom thumbaria-label / aria-labelledbyThe accessible name of the thumb. Use aria-labelledby to associate with a visible label.

Recommended Pattern (Good)

Good / Recommended

Start with <input type="range">. Just associate a label and you get the role, value, range, and keyboard interaction all at once.

Markup:

<label for="volume">Volume</label>
<input
  type="range"
  id="volume"
  name="volume"
  min="0"
  max="100"
  step="1"
  value="40" />
<output for="volume" id="volume-out">40</output>

Display sync (optional — the input itself handles announcements):

const input = document.getElementById('volume');
const out = document.getElementById('volume-out');

// Update the output whenever the value changes (for display; the input handles announcements)
input.addEventListener('input', () => {
  out.textContent = input.value;
});

Only build a custom slider with role="slider" when native styling truly cannot meet design requirements. In that case, you must provide the value, range, and keyboard interaction entirely on your own.

<!-- Only build a custom slider when native styling won't meet design requirements -->
<span id="bright-label">Brightness</span>
<div
  role="slider"
  tabindex="0"
  aria-labelledby="bright-label"
  aria-valuemin="0"
  aria-valuemax="100"
  aria-valuenow="40"
  aria-valuetext="40%"
  class="slider"
  id="bright-slider">
  <span class="slider-thumb"></span>
</div>
const slider = document.getElementById('bright-slider');
const MIN = 0, MAX = 100, STEP = 1, BIG = 10;

function setValue(next) {
  const v = Math.min(MAX, Math.max(MIN, next));
  slider.setAttribute('aria-valuenow', String(v));
  slider.setAttribute('aria-valuetext', v + '%'); // Provide a human-readable unit
  slider.style.setProperty('--pos', ((v - MIN) / (MAX - MIN)) * 100 + '%');
}

slider.addEventListener('keydown', (e) => {
  const now = Number(slider.getAttribute('aria-valuenow'));
  let next = now;
  switch (e.key) {
    case 'ArrowRight': case 'ArrowUp':   next = now + STEP; break;
    case 'ArrowLeft':  case 'ArrowDown': next = now - STEP; break;
    case 'PageUp':   next = now + BIG; break;
    case 'PageDown': next = now - BIG; break;
    case 'Home': next = MIN; break;
    case 'End':  next = MAX; break;
    default: return; // Let unrelated keys pass through
  }
  e.preventDefault();
  setValue(next);
});

Anti-pattern (Bad)

Below is a slider built by mouse-dragging a <div>.It works with a mouse, but is completely inoperable via keyboard, and the value is never announced to screen readers.

Broken slider: drag-only div
Brightness

Try it: pressing Tab does not focus the thumb. Arrow keys have no effect, and screen readers cannot identify this as a slider or read its current value.

<!-- ❌ Anti-pattern: drag-only div -->
<div class="track" onmousedown="startDrag(event)">
  <div class="knob" id="knob"></div>
</div>
<!--
  No role or tabindex, so it can't receive focus
  and can't be operated with a keyboard at all.
  No aria-valuenow either, so the current value is never announced.
-->

Bad / Avoid

Problems with this implementation:

  • Not keyboard-operable — A div cannot receive focus, so arrow keys cannot move the thumb.
  • No role conveyed — Without role="slider", it is not recognized as a slider.
  • No value or range conveyed — Without aria-valuenow / valuemin / valuemax, users cannot tell the current percentage.
  • No accessible name — There is no way to tell what this slider adjusts (e.g., brightness).

Implementation Checklist


Source (English):Slider Pattern — W3C APG(opens in a new tab)