Skip to Content
Component ExamplesSliders & Ratings

Overview

Range sliders and star ratings let users select a value from a continuous range or a discrete set. Custom slider implementations built from plain <div> elements are almost always inaccessible: they lack keyboard support, expose no ARIA role or value information, and provide no alternative to mouse dragging. The native <input type="range"> provides all of this for free. Star ratings similarly need the radio group pattern so assistive technology can announce the current selection and allow keyboard navigation between options.

WCAG Criteria:

Key requirements:

  • Use native <input type="range"> whenever possible — it provides keyboard, ARIA, and pointer support for free
  • Custom sliders must expose role="slider", aria-valuemin, aria-valuemax, aria-valuenow, and aria-valuetext
  • aria-valuetext is critical — it gives screen readers a human-readable value (e.g., “50 percent” instead of just “50”)
  • Star ratings should use the radio group pattern (role="radiogroup" with role="radio" children)
  • Arrow keys must adjust slider values and navigate between star rating options
  • Dragging must never be the only way to operate a control (WCAG 2.5.7)

Range Slider

Custom Drag-Only Slider vs. Native Range Input

Inaccessible
<!-- Custom div slider — drag only, no keyboard, no ARIA --> <span>Volume</span> <div class="track" onmousedown="startDrag(event)"> <div class="handle" style="left: 92px;"></div> </div>
Live Preview
Volume
Accessible
<!-- Native range input — keyboard, ARIA, and pointer for free --> <label for="volume">Volume</label> <input id="volume" type="range" min="0" max="100" value="50" oninput="document.getElementById('vol-out').textContent = this.value + '%'" /> <output id="vol-out">50%</output>
Live Preview
50%

What’s wrong with the custom drag-only slider?

  • No slider role — screen readers see a meaningless <div> with no interactive semantics
  • No aria-valuemin, aria-valuemax, or aria-valuenow — assistive technology cannot determine the current value or valid range
  • Not keyboard-focusable — there is no tabindex, so keyboard users cannot reach or operate the control
  • Arrow keys do nothing — the only interaction is mouse dragging
  • The <span> label is not programmatically associated with the control
  • Violates WCAG 2.5.7 because dragging is the only way to change the value

What the native range input gives you for free:

  • slider role exposed automatically in the accessibility tree
  • Arrow keys increment/decrement the value
  • Page Up/Down jump by larger steps
  • Home/End jump to min/max
  • The <label> is programmatically associated via for/id
  • Touch devices get a native thumb interaction

What the screen reader announces:

VersionAnnouncement
Inaccessible (custom div)“Volume” (just text — no role, no value, not interactive)
Accessible (native range)“Volume, slider, 50%” — arrow keys to adjust

Why the <output> element matters:

  • Provides a visible live value display that updates as the user drags or presses arrow keys
  • <output> has an implicit role="status" (aria-live="polite"), so screen readers announce value changes without moving focus
  • Sighted users and screen reader users both get real-time feedback on the current value

Star Rating

Click-Only Stars vs. Accessible Radio Group Stars

Inaccessible
<!-- Span stars with onclick — no keyboard, no semantics --> <span>Rating</span> <div> <span onclick="rate(1)">&#9733;</span> <span onclick="rate(2)">&#9733;</span> <span onclick="rate(3)">&#9733;</span> <span onclick="rate(4)">&#9733;</span> <span onclick="rate(5)">&#9733;</span> </div>
Live Preview
Rating
Accessible
<!-- Radio group pattern — keyboard nav and state announcement --> <div role="radiogroup" aria-label="Rating" onkeydown="handleArrowKeys(event)"> <span role="radio" aria-checked="false" aria-label="1 star" tabindex="0" onclick="selectStar(1)">&#9733;</span> <span role="radio" aria-checked="false" aria-label="2 stars" tabindex="-1" onclick="selectStar(2)">&#9733;</span> <span role="radio" aria-checked="false" aria-label="3 stars" tabindex="-1" onclick="selectStar(3)">&#9733;</span> <span role="radio" aria-checked="false" aria-label="4 stars" tabindex="-1" onclick="selectStar(4)">&#9733;</span> <span role="radio" aria-checked="false" aria-label="5 stars" tabindex="-1" onclick="selectStar(5)">&#9733;</span> </div>
Live Preview

What’s wrong with the click-only stars?

  • Plain <span> elements have no interactive role — screen readers announce them as static text (or skip them entirely)
  • No tabindex — keyboard users cannot focus or select a star
  • No aria-checked or equivalent state — screen readers cannot convey the current rating
  • The label “Rating” is just a visual <span> — it is not programmatically associated with the star group
  • onclick on a <span> only works with a mouse — no keyboard activation

How the radio group pattern works:

  • role="radiogroup" with aria-label gives the group an accessible name
  • Each star is role="radio" with aria-label (“1 star”, “2 stars”, etc.) and aria-checked
  • Roving tabindex: only the active star has tabindex="0", all others have tabindex="-1"
  • Arrow Right/Down moves to the next star; Arrow Left/Up moves to the previous star
  • This matches the WAI-ARIA radio group keyboard pattern that screen reader users expect

What the screen reader announces:

VersionAnnouncement
Inaccessible (span stars)Nothing useful — stars are invisible to assistive technology
Accessible (radio group)“Rating, radio group” then “3 stars, radio button, checked” on selection

Key teaching points:

  • aria-valuetext is critical for custom sliders — it gives screen readers human-readable values instead of raw numbers
  • Star ratings use the radio group pattern because each star is a mutually exclusive discrete choice
  • Arrow keys are the expected navigation method for both sliders and radio groups
  • Never rely on drag-only interaction — always provide keyboard and single-click alternatives

Resources