Announcing Dashboard Refreshes

Permalink to "Announcing Dashboard Refreshes"

Dashboards refresh: on a timer, when a filter changes, when the user presses Refresh. If each widget fetches and announces independently, a filter change produces a dozen overlapping messages — “Revenue updated”, “Orders updated”, “Chart updated” — of which the screen reader speaks one or two, chosen by timing. If nothing announces, users have no idea the numbers under their cursor have changed.

This page coordinates refreshes into one dashboard-level event, summarises what actually changed, and gives users control over automatic refresh. It belongs to data dashboards.

Spec reference

Permalink to "Spec reference"
  • SC 4.1.3 Status Messages (AA) — a refresh result is a status message; it must be announced without moving focus.
  • SC 2.2.2 Pause, Stop, Hide (A) — automatically updating information that starts automatically and is presented alongside other content must be pausable, stoppable or hideable (or its frequency controllable), unless it is essential. Auto-refreshing dashboards fall under this.
  • SC 2.2.1 Timing Adjustable (A) — if refreshes remove content (for example, rows disappearing from a “last 15 minutes” list), users may need more time.
  • SC 3.2.5 Change on Request (AAA) — changes of context only on user request; a useful target for dashboards that currently re-order or replace widgets on refresh.

The mechanics use the page’s shared status and alert regions and, for coordination, a promise that settles when all widget fetches for a refresh have finished.

One refresh, one message Flow of a coordinated dashboard refresh: trigger, all widgets fetch in parallel, results compared with previous values, a summary composed from meaningful changes, and one announcement. One refresh, one messageTriggertimer, filter orbuttonFetch allwidgets in parallelSettlePromise.allSettledDiffwhat changedmeaningfully?Announceone summary,polite
Widgets fetch in parallel but report to one coordinator, which speaks once.

When to announce a refresh — and how much

Permalink to "When to announce a refresh — and how much"

Announce every user-initiated refresh — a filter change or a Refresh button — because the user is waiting to know it worked. Summarise the result: “Updated for Region: North. Revenue €410k, down 3%. 2 widgets changed.”

For automatic refreshes, announce only meaningful changes, and let users choose: many prefer silence with a visible “Updated 10:05” timestamp they can read when they want, plus alerts for thresholds. Offer settings for off, alerts only, and summaries.

The misapplication to name is each widget component calling announce('Updated') after its own fetch. With twelve widgets, users hear a burst of identical words — or only the last one — and never learn which numbers moved.

Annotated code example

Permalink to "Annotated code example"
// Dashboard coordinator: widgets register fetchers; the coordinator speaks
const widgets = new Map();           // id -> { title, fetch, last, significant(prev, next) }
let autoRefresh = loadPref('dash-refresh') ?? 'alerts';   // 'off' | 'alerts' | 'summary'
let timer;

export function register(id, spec) { widgets.set(id, spec); }

export async function refresh({ reason }) {             // reason: 'user' | 'timer' | 'filter'
  const results = await Promise.allSettled(
    [...widgets].map(async ([id, w]) => [id, await w.fetch()]));

  const changes = [], failures = [], alerts = [];
  for (const r of results) {
    if (r.status === 'rejected') { failures.push(r.reason.widgetTitle); continue; }
    const [id, next] = r.value;
    const w = widgets.get(id);
    if (w.alert?.(next)) alerts.push(w.alert(next));
    if (w.last !== undefined && w.significant(w.last, next)) changes.push(w.describe(w.last, next));
    w.last = next;
    w.render(next);                                       // update in place: no remount, focus kept
  }

  setUpdatedAt(new Date());                               // visible "Updated 10:05"
  alerts.forEach((a) => announce(a, 'assertive'));        // thresholds: always

  const speak = reason !== 'timer' || autoRefresh === 'summary';
  if (!speak) return;
  const parts = [];
  if (changes.length) parts.push(changes.slice(0, 3).join('. '));
  if (changes.length > 3) parts.push(`${changes.length - 3} more widgets changed`);
  if (!changes.length) parts.push('No significant changes');
  if (failures.length) parts.push(`Could not update ${failures.join(', ')}`);
  announce(`Dashboard updated. ${parts.join('. ')}.`);    // SC 4.1.3: one message
}

// SC 2.2.2: users control automatic refresh
export function setAutoRefresh(mode, everyMs = 60000) {
  autoRefresh = mode; savePref('dash-refresh', mode);
  clearInterval(timer);
  if (mode !== 'off') timer = setInterval(() => refresh({ reason: 'timer' }), everyMs);
}
<div class="dash-refresh" role="group" aria-label="Refresh">
  <button type="button" onclick="refresh({ reason: 'user' })">Refresh now</button>
  <label for="auto">Automatic refresh</label>
  <select id="auto">
    <option value="off">Off</option>
    <option value="alerts" selected>On, announce alerts only</option>
    <option value="summary">On, announce summaries</option>
  </select>
  <p>Updated <time id="updated-at" datetime="2026-03-18T10:05">10:05</time></p>
</div>

significant(prev, next) is a product decision per widget — a revenue change of more than 1%, a new item in the top five, a status change — and it is the heart of a useful summary. Without it, every refresh “changes” every widget.

Keyboard & AT behaviour

Permalink to "Keyboard & AT behaviour"
Trigger Expected behaviour Announcement
Change region filter to North All widgets refresh in place “Dashboard updated. Revenue €410k, down 3%. Top account changed to Litware.”
Press Refresh, nothing changed Timestamp updates “Dashboard updated. No significant changes.”
Auto-refresh (alerts only) Silent update; timestamp changes Only threshold alerts, assertive
One widget fails Other widgets update “…Could not update Margin.”
Focus in a widget table during refresh Focus stays on the same cell Cell value re-read only if the user moves
A filter change, heard Timeline of a filter change on a twelve-widget dashboard: filter applied, twelve fetches in parallel, all settle, changes diffed, one summary announced. A filter change, heardRegion = Northuser changes the filter12 fetchesin parallel, silentAll settledincluding one failureDiff2 significant changesAnnounce"Dashboard updated. Revenue…"one coordinated refresh
Twelve fetches, one sentence — naming the changes that matter.

Integration context

Permalink to "Integration context"

The coordinator’s messages go through the page’s queue from announcement queues for competing messages, with alerts at urgent priority and summaries at high (user-initiated) or low (automatic). Continuous feeds inside a dashboard widget — a live ticker — follow real-time data stream announcements and should not also report through the refresh summary.

KPI values updated in place need atomic structure so a re-read gives the full sentence; see accessible KPI cards and sparklines.

Widget-level versus dashboard-level announcements Comparison of each widget announcing its own refresh against a coordinator announcing one summary of meaningful changes. Widget-level versus dashboard-level announcements✗ Each widget announces12 "Updated" messages per refreshReaders speak one or two at randomNo sense of what changedFailures lost among successes✓ Coordinator announcesOne message after all widgets settleNames only significant changesReports failures explicitlyUsers choose auto-refresh verbosity
The coordinator knows the whole picture, so it can say what matters and nothing else.

Gotchas

Permalink to "Gotchas"

Re-mounting widgets. Refreshing by re-rendering widgets with new keys destroys focus and scroll position inside them. Update data in place.

Refresh during interaction. If the user is typing in a widget’s filter or editing, defer that widget’s refresh until they finish.

Staggered timers. Widgets with different refresh intervals produce frequent small refreshes. Align them to a dashboard-wide interval, or treat each as a separate low-priority event.

Design system notes

Permalink to "Design system notes"

Provide a dashboard coordinator service and a widget contract: each widget registers fetch, render, significant and describe, and never announces on its own. The dashboard shell renders the refresh controls, the timestamp and the auto-refresh preference, so every dashboard offers the same SC 2.2.2 control.

Testing checklist

Permalink to "Testing checklist"

FAQ

Permalink to "FAQ"
How should a dashboard announce that it has refreshed?

With one message after all widgets have finished, summarising only the changes that matter — for example “Dashboard updated. Revenue down 3 percent. Top account changed.” Widgets should not each announce their own refresh.

Does an auto-refreshing dashboard need a pause control?

Under SC 2.2.2, automatically updating content shown alongside other content must be pausable, stoppable or controllable, unless it is essential. Offer a setting to turn automatic refresh off or change its frequency.

Should automatic refreshes be announced at all?

Often not by default. A visible timestamp plus announced threshold alerts is a good default; let users opt into spoken summaries.

What if a widget fails to refresh?

Keep its previous data visible with a clear stale indicator, and mention the failure in the summary so users know that widget is out of date.

Permalink to "Related"

← Back to Data Dashboards