Announcing View Changes in Data Apps
Permalink to "Announcing View Changes in Data Apps"Data apps switch views constantly: table and chart, list and board, grouped and ungrouped, saved view A and saved view B, day and week. For sighted users the change is obvious — the whole area redraws. For screen reader users the switch is often silent beyond the control’s own state (“Board, tab, selected”) and they must explore to discover what the new view contains. The right announcement fills that gap without duplicating the control or stealing focus.
This guide belongs to tabs & view switchers for data views.
Spec reference
Permalink to "Spec reference"- SC 4.1.3 Status Messages (AA) — information about the result of an action that does not move focus — “42 rows, grouped by status” — is a status message.
- SC 3.2.2 On Input (A) — switching views on selection is fine as long as it does not unexpectedly move focus or open new windows.
- SC 2.4.2 Page Titled (A) — when a view switch changes the page’s purpose (a different report), update the title.
- SC 4.1.2 Name, Role, Value (A) — the switcher’s own state is announced by the control; do not repeat it in a live region.
When to use — and when not to
Permalink to "When to use — and when not to"Announce when the view’s content differs in a way users cannot infer from the control: a different number of items, a different grouping, columns hidden or shown, a filter included in the view. Announce when the switch takes noticeable time (“Loading board…” then “Board loaded, 12 cards”).
Do not announce when the control’s state already says everything — switching a density radio from comfortable to compact needs no message. Do not announce when the app restores a view on page load; the page’s normal reading covers it.
The misapplication: a live region that repeats the tab name. Users hear “Board, tab, selected” and then “Board view” — twice the speech, no new information.
Annotated code example
Permalink to "Annotated code example"const status = document.getElementById('view-status'); // role="status", present on load
let userInitiated = false;
switcher.addEventListener('pointerdown', () => { userInitiated = true; });
switcher.addEventListener('keydown', () => { userInitiated = true; });
async function onViewChange(view) {
const announceAfter = userInitiated;
userInitiated = false;
if (view.slow && announceAfter) status.textContent = `Loading ${view.label}…`;
await render(view);
if (!announceAfter) return; // no announcements on load or restore
status.textContent = summary(view); // only new information (SC 4.1.3)
if (view.major) document.title = `${view.label} — Invoices — Acme`; // SC 2.4.2
}
function summary(view) {
switch (view.kind) {
case 'board': return `${view.cardCount} cards in ${view.columns.length} columns: ${view.columns.join(', ')}.`;
case 'chart': return `${view.chartType} of ${view.seriesCount} series. Table view also available.`;
case 'table': return `${view.rowCount} rows${view.groupBy ? `, grouped by ${view.groupBy}` : ''}.`;
}
}
Keyboard & AT behaviour
Permalink to "Keyboard & AT behaviour"| Event | Expected announcement | AT deviations |
|---|---|---|
| Select “Board” tab | “Board, tab, selected, 2 of 3” then “12 cards in 4 columns: To do, Doing, Review, Done.” | Status may be queued behind the tab announcement — expected |
| Select “Chart” | “Chart, tab, selected” then “Bar chart of 8 regions…” | — |
| Page load with saved view | Normal page reading | Nothing extra |
| Slow view | “Loading board…” then summary | Clear the loading message if the view loads under ~500 ms |
Integration context
Permalink to "Integration context"Tabs between table and chart are covered in the tabs pattern for switching table and chart views; saved views in saved views menus for data grids. Route-level changes in single-page apps follow managing route change focus in Angular and Vue. Keep summaries in the shared queue described in announcement queues for competing messages.
Gotchas
Permalink to "Gotchas"Detecting user intent. Distinguish user-initiated switches from programmatic ones (URL restore, default view). The flag approach above is simple; a more robust one passes { source: 'user' } through your state store.
Summary length. Listing ten board columns is too long. Count them, and name them only if there are four or fewer.
Focus jumps. Some apps move focus to the first item of the new view. That is acceptable only when the user explicitly asked to “go to” the view; otherwise keep focus on the switcher.
Framework notes
Permalink to "Framework notes"In React, put the announcer at the app root so a remount of the view does not recreate the live region; pass source in your view-change action. In Angular, view switches inside one route are usually component swaps — use the CDK LiveAnnouncer. In Vue, a watch on the view key with { flush: 'post' } runs after the DOM updates, which is when the summary is accurate.
// Vue: announce after DOM update, only for user switches
watch(() => store.view, (v, old) => { if (store.lastSource === 'user') announce(summary(v)); }, { flush: 'post' });
Worked example
Permalink to "Worked example"A project tool offers List, Board and Timeline views. Switching to Board said “Board, tab, selected” and nothing else; a screen reader user had to explore to learn there were five columns and 38 cards, two of them blocked. The team added summaries computed from the rendered view — “38 cards in 5 columns. 2 blocked.” — and removed an older “View changed” message that had been firing on page load too. The Timeline view, which loads slowly, announces “Loading timeline…” first, but only when loading takes more than half a second.
Testing checklist
Permalink to "Testing checklist"Design system notes
Permalink to "Design system notes"Give every view a summary() function as part of its contract and let the view switcher component handle timing, intent detection and the live region. That keeps announcements consistent across views built by different teams.
FAQ
Permalink to "FAQ"Is the tab or radio state not enough?
It says which view is selected, not what it contains. Users need the content summary to decide whether to explore.
Polite or assertive?
Polite. View changes are expected results of the user’s action.
Should loading states be announced?
Only when loading is slow enough to notice — more than about half a second. Otherwise the loading message and the result collide.
Should the URL change on view switch?
Usually yes, with replaceState for minor switches and pushState for major ones, so Back behaves sensibly.
What about views switched by keyboard shortcuts?
Treat them as user-initiated and announce the summary; the shortcut gives no visual cue at all.
How do I announce a change to a chart view without describing the whole chart?
Give the chart type, what it plots and the headline: “Line chart of daily orders over 30 days; highest on 14 September.” The chart’s own text alternative holds the detail.
Related
Permalink to "Related"- Table and chart view tabs — the switcher
- Saved views menus — switching configurations
- Announcement queues — ordering messages