Dashboard Filters That Apply Across Widgets
Permalink to "Dashboard Filters That Apply Across Widgets"Global filters change everything at once: pick “Last 30 days” and twelve widgets reload. Sighted users watch charts redraw. Screen reader users hear either nothing or a burst of twelve separate “loading” and “updated” messages. And when some widgets ignore a filter — a “Year to date” widget that is not affected by the date range — nobody can tell. Accessible global filters need a clear filter bar, one consolidated announcement and widget-level filter labels.
This guide belongs to accessible data dashboards.
Spec reference
Permalink to "Spec reference"- SC 1.3.1 (A) — filter controls grouped and labelled.
- SC 3.2.2 On Input (A) — changing a filter must not cause unexpected context changes; apply buttons or predictable auto-apply.
- SC 4.1.3 (AA) — a single status message summarises the update.
- SC 2.4.6 (AA) — widget headings and labels say what they show, including filters.
When to use — and when not to
Permalink to "When to use — and when not to"Use global filters when most widgets share dimensions such as date or region. Put widget-specific filters inside widgets.
Do not auto-apply complex filter changes on every keystroke; with many widgets, each change triggers heavy reloads and announcements. The misapplication: each widget owning its own live region, so a single filter change produces a dozen overlapping announcements.
Annotated code example
Permalink to "Annotated code example"<section aria-labelledby="filters-h" class="filter-bar">
<h2 id="filters-h">Dashboard filters</h2>
<label for="f-range">Date range</label>
<select id="f-range"><option>Last 7 days</option><option selected>Last 30 days</option></select>
<label for="f-region">Region</label>
<select id="f-region"><option selected>All regions</option><option>North</option></select>
<button type="button" id="apply">Apply filters</button>
</section>
<p role="status" id="dash-status"></p>
<section aria-labelledby="w-rev" aria-busy="false" class="widget">
<h3 id="w-rev">Revenue</h3>
<p class="widget-filters">Last 30 days · All regions</p> <!-- filters this widget uses -->
</section>
async function applyFilters(f) {
widgets.forEach(w => w.el.setAttribute('aria-busy', 'true'));
const results = await Promise.allSettled(widgets.map(w => w.load(f)));
widgets.forEach(w => w.el.setAttribute('aria-busy', 'false'));
const failed = results.filter(r => r.status === 'rejected').length;
status.textContent = `Dashboard updated for ${f.rangeLabel}, ${f.regionLabel}.` +
(failed ? ` ${failed} widgets could not load.` : ''); // one message
}
Keyboard & AT behaviour
Permalink to "Keyboard & AT behaviour"| Action | Focus | Announcement |
|---|---|---|
| Change date range select | Select | Option name |
| Activate Apply filters | Button | “Dashboard updated for last 30 days, all regions.” |
| Some widgets fail | Button | “… 2 widgets could not load.” |
| Navigate to a widget | Widget heading | “Revenue, heading” then “Last 30 days · All regions” |
| Widget ignores range | — | Subtitle “Year to date (not affected by date range)” |
Step-by-step implementation
Permalink to "Step-by-step implementation"- Group filters. Put them in a section with a heading so screen reader users can jump to them, and keep the order stable.
- Decide apply behaviour. Use an Apply button for multiple filters or slow dashboards; auto-apply with a short debounce for a single fast filter.
- Mark widgets busy. Set
aria-busyon widgets while they load and show visual loading states. - Announce once. After all widgets settle (or a timeout), announce a single summary including any failures.
- Label widget scope. Each widget shows the filters it uses, and states when a global filter does not apply.
Integration context
Permalink to "Integration context"Refresh announcements are covered in announcing dashboard refreshes. Filter controls follow accessible filtering and faceted search, date ranges accessible date range filters, and applied filter summaries applied filter chips and removal. Loading states are in using aria-busy during progressive table loads.
Gotchas
Permalink to "Gotchas"Slow widgets. One slow widget delays the summary; announce after a timeout and mention widgets still loading.
Cross-filtering. Clicking a chart bar to filter other widgets is a global filter too; it needs a keyboard equivalent and the same announcement.
URL state. Put filters in the URL so dashboards can be shared and the Back button restores them.
Edge cases
Permalink to "Edge cases"- No data. If a filter combination leaves widgets empty, say so in the summary: “4 widgets have no data for North”.
- Conflicting widget filters. If a widget has its own filter that overrides the global one, show both.
- Saved views. Loading a saved view changes several filters at once; announce the view name and the filters.
Worked example
Permalink to "Worked example"A sales dashboard had a date range and region filter that auto-applied on change, and each of ten widgets announced “Updated” through its own live region. Screen reader users heard a jumble of ten messages and could not tell which widgets ignored the region filter. The team added an Apply button, a single summary message, aria-busy per widget and a subtitle in each widget stating its filters. Users heard one clear message and could see which numbers were regional.
Common mistakes
Permalink to "Common mistakes"- Live regions in every widget.
- Unlabelled filter bars.
- Widgets that silently ignore filters.
- Auto-applying slow filters on every change.
Testing checklist
Permalink to "Testing checklist"Testing it with a screen reader
Permalink to "Testing it with a screen reader"With NVDA, jump to the “Dashboard filters” heading, change the date range and activate Apply. You should hear one summary message, not a series. Navigate widget headings with H and confirm each subtitle states the filters. Simulate a widget failure and confirm it is mentioned in the summary. In VoiceOver, repeat and check that aria-busy widgets are not read mid-update.
Cross-filtering from charts
Permalink to "Cross-filtering from charts"Many dashboards let users click a bar or slice to filter every other widget. That is a global filter set from inside a widget, and it needs the same treatment: a keyboard way to select the bar (see chart keyboard navigation guides), a visible chip in the filter bar showing “Region: North (from Revenue chart)”, and the same single summary announcement. Make the chip removable, and return focus to the chart element after removal so users can choose another.
Writing the summary message
Permalink to "Writing the summary message"The summary should answer two questions: what is the dashboard showing now, and did anything go wrong? Lead with the filters in plain words (“last 30 days, North region”), not control names. Mention failures and empty widgets by count, and name them only if there are one or two. Leave out widgets that updated normally; users can read them. If the update took long enough that the user may have moved on, keep the message polite so it does not interrupt their reading. And use the same structure every time, so users learn to listen for the last clause when something failed.
Key points
Permalink to "Key points"- One filter bar, one heading, one summary message per change.
- Widgets go busy while loading and stay quiet.
- Every widget states the filters it uses, and says when it ignores one.
Design system notes
Permalink to "Design system notes"Provide a dashboard filter context that widgets subscribe to, with a shared announcer and a standard widget subtitle showing applied filters.
FAQ
Permalink to "FAQ"Should filters auto-apply?
For one fast filter, yes with a debounce; otherwise use an Apply button.
How many announcements per change?
One.
Where should filters be?
At the top, before widgets, in a labelled section.
How do users know a widget ignores a filter?
The widget subtitle says so.
What about filters inside widgets?
They apply locally and announce locally.
Should focus move after applying?
No; keep it on the Apply button.
Related
Permalink to "Related"- Announcing dashboard refreshes — refresh messages
- Dashboard layout customization — arranging widgets
- Applied filter chips and removal — showing active filters