Announcing Numeric Changes with Direction and Magnitude

Permalink to "Announcing Numeric Changes with Direction and Magnitude"

A KPI tile changes from 1,240 to 1,310 and turns green with an up arrow. Sighted users read “up, a little” in a glance. A screen reader user hears either nothing or “1,310”, with no sense of whether that is good, bad, big or small. Useful announcements state the new value, the direction in words and the size of the change: “Open orders: 1,310, up 70 (5.6%) since 10:00”. They are also rare enough to be heard: only changes that cross a threshold, coalesced and rate-limited.

This guide belongs to ARIA live regions for dynamic data.

Spec reference

Permalink to "Spec reference"
  • SC 4.1.3 Status Messages (AA) — status messages announced without focus.
  • SC 1.4.1 Use of Color (A) — direction not conveyed only by green and red.
  • SC 1.1.1 (A) — arrow icons need text equivalents (or are decorative when text conveys direction).
  • SC 2.2.2 Pause, Stop, Hide (A) — auto-updating information must be controllable.
From data change to a useful announcement Flow: a new value arrives; the app compares it to the last announced value; if the change crosses a threshold it formats value, direction and magnitude; a rate limiter coalesces messages; the polite region speaks. From data change to a useful announcementNew value1,310 (was 1,240)Threshold checkchange ≥ 5%? yesFormat"up 70 (5.6%)"Rate limitmax 1 per 30 s per metricPolite region"Open orders: 1,310, up 70"
Most updates update the display only; few reach the live region.

When to use — and when not to

Permalink to "When to use — and when not to"

Announce changes that users are monitoring and that matter for their task: a queue length crossing a limit, an error rate rising, a balance changing after their own action. Pair with a pause control and a setting to choose which metrics speak.

Do not announce every tick of a fast-changing value; it drowns everything else. The misapplication: wrapping a whole KPI row in aria-live="polite", so every refresh reads six numbers with no direction or context.

Raw value announcements versus direction and magnitude Comparison: announcing raw new values every refresh is noisy and meaningless; thresholded announcements with direction, magnitude and context are rare and useful. Raw value announcements versus direction and magnitude✗ Every refresh announced"1,310"Colour and arrow onlySix metrics at onceNo way to stop✓ Only threshold crossings"Open orders: 1,310, up 70 (5.6%)"Direction in wordsOne metric per messagePause and per-metric settings

Annotated code example

Permalink to "Annotated code example"
const fmt = new Intl.NumberFormat('en-GB');
const pct = new Intl.NumberFormat('en-GB', { style: 'percent', maximumFractionDigits: 1, signDisplay: 'never' });
const lastSpoken = new Map();

function maybeAnnounce(metric, value) {
  const prev = lastSpoken.get(metric.id) ?? metric.baseline;
  const delta = value - prev, ratio = prev ? delta / prev : 0;
  if (Math.abs(ratio) < metric.threshold) return;                          // below threshold: display only
  const dir = delta > 0 ? 'up' : 'down';
  const judgement = metric.goodWhen === dir ? '' : ' — attention';          // optional meaning, in words
  queue.push(`${metric.name}: ${fmt.format(value)}, ${dir} ${fmt.format(Math.abs(delta))} (${pct.format(ratio)})${judgement}`);
  lastSpoken.set(metric.id, value);
}
<div class="kpi">
  <h3 id="k-orders">Open orders</h3>
  <p><span class="value">1,310</span>
     <span class="delta"><svg viewBox="0 0 20 20" aria-hidden="true">…▲…</svg> up 70 (5.6%) since 10:00</span></p>   <!-- visible words too -->
</div>
<div role="status" class="sr-only" id="kpi-announcer"></div>

Keyboard & AT behaviour

Permalink to "Keyboard & AT behaviour"
Situation Announcement
Small change under threshold None; display updates
Threshold crossed “Open orders: 1,310, up 70 (5.6%)”
Several metrics cross together Queued, one per message, most important first
Announcements paused None; a visible “Paused” state
User focuses a KPI Full value and change read from the tile
Announcements per hour by strategy Bar chart of announcements per hour for a dashboard with six metrics updating every 5 seconds: announcing every update 4,320; thresholded 60; thresholded and rate-limited 12. Announcements per hour by strategyEvery updateannouncements perhourThreshold 5%announcements per hourThreshold + rate limitannouncements per hour
Thresholds and rate limits turn noise into signal.

Step-by-step implementation

Permalink to "Step-by-step implementation"
  1. Pick metrics that may speak. Not every number on a dashboard deserves an announcement. Choose the few users actively monitor, and make the list user-configurable.
  2. Set thresholds per metric. A 5% change in revenue might matter; a 5% change in page views might not. Allow absolute thresholds for small numbers, where percentages exaggerate.
  3. Format for listening. Name, new value, direction word, absolute change, percentage, and time base if relevant. Round to the precision users care about.
  4. Coalesce and limit. Use a queue that keeps only the latest message per metric and spaces messages by several seconds.
  5. Give control. Provide “Pause announcements” and per-metric toggles, and remember the choices.

Integration context

Permalink to "Integration context"

Live region basics are in choosing between polite and assertive aria-live regions and queuing in announcement queues for competing messages. Streaming data is covered in ticker and price feed announcements, and user control in verbosity settings for data announcements.

Gotchas

Permalink to "Gotchas"

Percentages of zero. A change from 0 has no percentage; say “up 12 from zero”.

Good and bad. Up is not always good (error rate). If you convey judgement, use words like “attention” rather than relying on colour.

Locales. Format numbers and percentages with the user’s locale; “1.310” means something different in Norwegian and English.

Edge cases

Permalink to "Edge cases"
  • Oscillating values. A value bouncing around a threshold triggers repeated messages; add hysteresis so it must move back by a margin before re-announcing.
  • Hidden tabs. When the page is hidden, stop announcing and summarise on return: “While away: open orders up 210”.
  • User-caused changes. After the user’s own action, announce the result immediately, ignoring thresholds.

Worked example

Permalink to "Worked example"

A support operations dashboard updated six KPI tiles every five seconds, each wrapped in a polite live region. Screen reader users heard a constant stream of numbers and turned the screen reader off. The team removed the live regions from tiles, added a single announcer with per-metric thresholds, direction words and a 30-second rate limit, and a settings panel to choose metrics. Agents using NVDA kept announcements on for queue length and SLA breaches only and said the dashboard became usable for the first time.

Common mistakes

Permalink to "Common mistakes"
  • Live regions around every tile.
  • Raw numbers with no direction.
  • Colour as the only good/bad cue.
  • No pause or settings.

Testing checklist

Permalink to "Testing checklist"

Testing it with a screen reader

Permalink to "Testing it with a screen reader"

Run the dashboard with simulated data that changes every second. With NVDA, sit on the page for five minutes and count announcements; there should be only a handful, each understandable on its own. Push one metric past its threshold and confirm the message wording, then make it oscillate to verify hysteresis. Toggle “Pause announcements” and confirm silence. Repeat with VoiceOver, which can drop messages that arrive too quickly, and check that the queue spaces them enough to be heard.

Writing announcement text

Permalink to "Writing announcement text"

Read every message aloud before shipping it. Spoken numbers are slower than read ones, so drop unnecessary precision: “1.3 million” is quicker than “1,312,408”. Put the metric name first, so a user who only catches the start still knows what changed. Use the same direction words everywhere — “up” and “down”, or “increased” and “decreased” — rather than mixing synonyms. And keep a time base where it matters: “up 70 since 10:00” is clearer than “up 70” when updates are irregular.

Design system notes

Permalink to "Design system notes"

Provide a KPI component with an announce policy (off, threshold, always) and a shared announcer service, rather than live regions inside each tile.

FAQ

Permalink to "FAQ"
Should KPI tiles be live regions?

No. Use one shared announcer and keep tiles as static content.

Polite or assertive?

Polite, except for critical alerts the user asked for.

How often is too often?

More than one message every few seconds is too often for most users.

Should I announce percentage or absolute change?

Both when both are meaningful; absolute alone for small numbers.

How do I convey good or bad?

In words (“attention”, “within target”), never by colour alone.

What if the user is typing?

Polite messages wait; still keep them rare so they do not pile up.

Permalink to "Related"

← Back to ARIA Live Regions for Dynamic Data