Range Slider Filters for Numeric Columns
Permalink to "Range Slider Filters for Numeric Columns"Range sliders are a compact way to filter numeric columns — “amount between 500 and 2,000” — and a common accessibility failure. Two thumbs on one track look like one control, but each thumb is its own slider with its own value, limits and keyboard behaviour. Screen reader users need each thumb named (“Minimum amount”, “Maximum amount”) and values spoken in units. Keyboard users need steps that reach the far end of a large range without 2,000 key presses. And everyone benefits from paired text inputs for exact values.
This guide belongs to accessible filtering and faceted search.
Spec reference
Permalink to "Spec reference"- ARIA slider pattern —
role="slider"witharia-valuemin,aria-valuemax,aria-valuenowandaria-valuetext; for multi-thumb sliders, each thumb is a separate slider. - SC 2.1.1 Keyboard (A) — every thumb operable by keyboard.
- SC 2.5.7 Dragging Movements (AA) — dragging thumbs needs a single-pointer alternative (clicking the track, or the paired inputs).
- SC 4.1.2 (A) — names and values exposed.
- SC 4.1.3 (AA) — the result count after filtering announced politely.
When to use — and when not to
Permalink to "When to use — and when not to"Use a range slider when users explore a continuous numeric range and approximate values are fine — price ranges, amount bands, durations. Always pair it with number inputs for exact values.
Do not use a slider alone when values must be precise (exact invoice amounts) or the range is enormous (0 to 10 million); text inputs are faster. The misapplication: two native input type="range" elements stacked with CSS so that only one thumb receives pointer events, leaving the other unreachable.
Annotated code example
Permalink to "Annotated code example"<fieldset class="range-filter">
<legend>Amount (kroner)</legend>
<div class="track">
<div role="slider" tabindex="0" aria-label="Minimum amount"
aria-valuemin="0" aria-valuemax="5000" aria-valuenow="500" aria-valuetext="500 kroner"></div>
<div role="slider" tabindex="0" aria-label="Maximum amount"
aria-valuemin="0" aria-valuemax="5000" aria-valuenow="2000" aria-valuetext="2,000 kroner"></div>
</div>
<label>Minimum <input type="number" id="amt-min" value="500" min="0" max="5000" step="10"></label>
<label>Maximum <input type="number" id="amt-max" value="2000" min="0" max="5000" step="10"></label>
</fieldset>
<p role="status" id="filter-count"></p>
function onKey(e, thumb) {
const step = e.key.startsWith('Page') ? 500 : 10; // larger jumps with Page keys
let v = thumb.value;
if (e.key === 'ArrowRight' || e.key === 'ArrowUp' || e.key === 'PageUp') v += step;
if (e.key === 'ArrowLeft' || e.key === 'ArrowDown' || e.key === 'PageDown') v -= step;
if (e.key === 'Home') v = thumb.min; if (e.key === 'End') v = thumb.max;
thumb.set(clamp(v, thumb.min, thumb.max)); // min thumb's max = max thumb's value
scheduleFilter(); // debounced
e.preventDefault();
}
Keyboard & AT behaviour
Permalink to "Keyboard & AT behaviour"| Key | Effect | Announcement |
|---|---|---|
| Right / Up Arrow | +10 | “510 kroner” |
| Left / Down Arrow | −10 | “490 kroner” |
| Page Up / Page Down | ±500 | “1,000 kroner” |
| Home / End | Thumb’s current minimum / maximum | “0 kroner” |
| Pause after change | Filter applies | “84 invoices match” |
Step-by-step implementation
Permalink to "Step-by-step implementation"- Model two values with constraints. The minimum thumb’s effective maximum is the maximum thumb’s value, and vice versa. Update
aria-valuemaxon the minimum thumb andaria-valueminon the maximum thumb as they move, so assistive technology knows the real limits. - Write value text with units.
aria-valuetextshould say “2,000 kroner” or “3 hours”, formatted for the locale, not a bare number. - Sync the number inputs. Typing in an input moves its thumb; moving a thumb updates its input. Validate typed values on change and explain errors next to the input.
- Debounce the filter. Apply the filter after a short pause in key presses, so holding an arrow key does not trigger dozens of fetches and announcements.
- Announce the result. Update the status region with the number of matching rows, and include the range if helpful: “84 invoices between 500 and 2,000 kroner”.
Integration context
Permalink to "Integration context"Result count announcements follow announcing filter result counts with aria-live. Date ranges are covered in accessible date range filters. Applied ranges should appear as chips, as described in applied filter chips and removal. Headless slider primitives are compared in headless UI primitives for data.
Gotchas
Permalink to "Gotchas"Focus visibility on thumbs. Thumbs are small; give them a focus ring that meets 3:1 and is not clipped by the track.
Vertical orientation. If the slider is vertical, set aria-orientation="vertical" and make Up increase the value.
Touch screen readers. VoiceOver uses swipe up and down to adjust sliders; test that your key handler is not the only way values change (native increment events or paired inputs help).
Edge cases
Permalink to "Edge cases"- Thumbs at the same value. When both thumbs sit on one value, make sure each remains focusable and the visual stacking does not hide the focused one.
- Logarithmic scales. For prices from 1 to 1,000,000, use a non-linear scale and say so in the legend; value text still reports real values.
- Empty results. If the range matches nothing, announce “No invoices match” and suggest widening the range.
Worked example
Permalink to "Worked example"A procurement app filtered purchase orders with a two-thumb amount slider built from overlapping native range inputs. Pointer users could not grab the minimum thumb when the thumbs were close, and NVDA users heard two controls both called “Amount” with raw numbers. The team rebuilt it as two named sliders with currency value text, Page keys of 500, synced number inputs and a debounced count announcement. Keyboard users could now set “between 10,000 and 50,000” in five key presses.
Common mistakes
Permalink to "Common mistakes"- Both thumbs with the same name.
- No units in value text.
- Filtering on every key press.
- No text inputs for exact values.
Testing checklist
Permalink to "Testing checklist"Testing it with a screen reader
Permalink to "Testing it with a screen reader"With NVDA and Firefox, tab to the minimum thumb and listen for “Minimum amount, slider, 500 kroner”. Press Right Arrow a few times and confirm each value is spoken, then pause and listen for one result count message. Press End and confirm the thumb stops at the maximum thumb’s value. Tab to the maximum thumb and repeat. In VoiceOver on iOS, swipe up and down on each thumb to adjust values, and check that the paired number inputs are reachable for exact entry. Finally, confirm that holding an arrow key does not produce a flood of count announcements.
Design system notes
Permalink to "Design system notes"Ship a range filter component that composes two sliders, paired inputs, a fieldset legend and a debounced status message; require units in its API so value text is always meaningful.
FAQ
Permalink to "FAQ"Can I use native input type="range"?
For a single thumb, yes. Two native ranges overlapped with CSS are fragile; use two separate sliders or a tested component.
What step size should I use?
One that matches meaningful differences in the data, with Page keys for roughly a tenth of the range.
Should the slider filter immediately?
Update the value immediately, filter after a short pause.
How do I show the selected range visually?
Highlight the track between the thumbs and show both values as text near the slider.
Do I need aria-valuetext?
Yes, whenever the number alone lacks units or formatting.
What about dates?
Date ranges are better served by date inputs; see the date range filter guide.
Related
Permalink to "Related"- Accessible date range filters — ranges of dates
- Applied filter chips and removal — showing the active range
- Announcing filter result counts — the count message