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:
- 4.1.2 Name, Role, Value — interactive elements must expose their role, name, and current value
- 1.3.1 Info and Relationships — information conveyed visually must be programmatically determinable
- 2.5.7 Dragging Movements — functionality that uses dragging must have a single-pointer alternative
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, andaria-valuetext aria-valuetextis 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"withrole="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
<!-- 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><!-- 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>What’s wrong with the custom drag-only slider?
- No
sliderrole — screen readers see a meaningless<div>with no interactive semantics - No
aria-valuemin,aria-valuemax, oraria-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:
sliderrole 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 viafor/id - Touch devices get a native thumb interaction
What the screen reader announces:
| Version | Announcement |
|---|---|
| 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 implicitrole="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
<!-- Span stars with onclick — no keyboard, no semantics -->
<span>Rating</span>
<div>
<span onclick="rate(1)">★</span>
<span onclick="rate(2)">★</span>
<span onclick="rate(3)">★</span>
<span onclick="rate(4)">★</span>
<span onclick="rate(5)">★</span>
</div><!-- 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)">★</span>
<span role="radio" aria-checked="false"
aria-label="2 stars" tabindex="-1"
onclick="selectStar(2)">★</span>
<span role="radio" aria-checked="false"
aria-label="3 stars" tabindex="-1"
onclick="selectStar(3)">★</span>
<span role="radio" aria-checked="false"
aria-label="4 stars" tabindex="-1"
onclick="selectStar(4)">★</span>
<span role="radio" aria-checked="false"
aria-label="5 stars" tabindex="-1"
onclick="selectStar(5)">★</span>
</div>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-checkedor 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 onclickon a<span>only works with a mouse — no keyboard activation
How the radio group pattern works:
role="radiogroup"witharia-labelgives the group an accessible name- Each star is
role="radio"witharia-label(“1 star”, “2 stars”, etc.) andaria-checked - Roving tabindex: only the active star has
tabindex="0", all others havetabindex="-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:
| Version | Announcement |
|---|---|
| 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-valuetextis 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