Skip to content

Stat Card

DashboardSince 0.4.0
import { StatCard } from '@absoluteui/components/stat-card';
import '@absoluteui/components/stat-card/style.css';

Using Angular? See Stat Card for Angular.

A KPI / metric card. StatCard renders a headline value with its label, an optional change against a previous value, a comparison caption, an optional trend Sparkline, and supporting text. The change shows an arrow and a signed amount and is spelled out for assistive technology (“increase of 20%, favorable”; the direction word carries the sign, since 0.9.0), so its meaning never depends on color. sentiment decides whether an increase is favorable. The card is a role="group" named by its label; with href the whole card is one link.

  • Headline metrics on dashboards: revenue, active users, error rate, with the change since the previous period.
  • KPI tiles that link to a detail page (href).
  • Comparing many values or showing a series in detail: use a chart or Data Table.
  • Progress toward a goal: use Progress Bar (or a gauge chart).
  • Cards with several interactive controls: use Card.

Revenue with the change against last month and a trend

Section titled “Revenue with the change against last month and a trend”
import { StatCard } from '@absoluteui/components/stat-card';
const host = document.createElement('div');
document.body.append(host);
const card = StatCard.create(host, {
label: 'Revenue',
value: 48200,
previous: 40100,
valueFormat: { style: 'currency', currency: 'USD', maximumFractionDigits: 0 },
comparisonLabel: 'vs last month',
trend: [31000, 35500, 38200, 40100, 48200],
});
host.dataset.delta = String(card.delta);

Error rate where a decrease is favorable, linking to details

Section titled “Error rate where a decrease is favorable, linking to details”
import { StatCard } from '@absoluteui/components/stat-card';
const host = document.createElement('div');
document.body.append(host);
StatCard.create(host, {
label: 'Error rate',
value: 0.012,
valueFormat: { style: 'percent', maximumFractionDigits: 1 },
delta: -0.003,
deltaFormat: 'absolute',
sentiment: 'lower-is-better',
comparisonLabel: 'vs yesterday',
href: '/reports/errors',
});
import { StatCard } from '@absoluteui/components/stat-card';
const host = document.createElement('div');
document.body.append(host);
const card = StatCard.create(host, { label: 'Active users', value: null, busy: true });
export function showActiveUsers(count: number, previous: number): void {
card.update({ busy: false, value: count, previous });
}
import { configureAbsoluteUI } from '@absoluteui/core';
configureAbsoluteUI({ components: { statCard: { deltaFormat: 'absolute' } } });
import { StatCard } from '@absoluteui/components/stat-card';
import '@absoluteui/components/stat-card/style.css';
import '@absoluteui/components/sparkline/style.css';
const host = document.querySelector<HTMLElement>('#errors');
if (host) {
StatCard.create(host, {
label: 'Error rate',
value: 0.012,
valueFormat: { style: 'percent', maximumFractionDigits: 1 },
previous: 0.015,
sentiment: 'lower-is-better',
comparisonLabel: 'vs yesterday',
trend: [0.02, 0.018, 0.015, 0.012],
href: '/reports/errors',
});
}
import { StatCard } from '@absoluteui/components/stat-card';
const host = document.querySelector<HTMLElement>('#revenue');
if (host) StatCard.create(host, { label: 'Revenue', value: 48200, previous: 40100, valueFormat: { style: 'currency', currency: 'USD', maximumFractionDigits: 0 }, comparisonLabel: 'vs last month' });
<div class="aui-stat-card"> (consumer host; children replaced)
<div class="aui-stat-card__body" role="group" aria-labelledby> (<a class="aui-stat-card__body aui-stat-card__link" href> with href)
<span class="aui-stat-card__top">[icon.aui-stat-card__icon]<span class="aui-stat-card__label" id>Revenue</span></span>
<span class="aui-stat-card__value">$48,200</span>
<span class="aui-stat-card__change"> (when there is a change)
<span class="aui-stat-card__delta aui-stat-card__delta--good|--bad|--neutral">
[arrow.aui-stat-card__arrow]<span aria-hidden="true">+20.2%</span><span class="aui-stat-card__sr">increase of 20.2%, favorable</span>
</span>
<span class="aui-stat-card__comparison"> vs last month</span>
</span>
<span class="aui-stat-card__trend">(Sparkline)</span> (trend)
<span class="aui-stat-card__description"> (description)
StatCard.create(host: HTMLElement, options: StatCardOptions): StatCard

Global configuration key: statCard (see Configuration).

Option Type Default Description
label string Metric name; names the card (group) and, by default, the trend sparkline. Non-empty. Required.
value number | string | null A number (formatted with valueFormat or formatValue), preformatted text, or null (‘No value’). Required.
previous number | null Previous value; the change is computed from it when value is a number (percent: (value - previous) / |previous|, not shown for 0; absolute: value - previous). Finite.
delta number | null Explicit change (a ratio for ‘percent’, units for ‘absolute’); wins over previous. Finite.
comparisonLabel string Caption after the change, for example ‘vs last month’ (shown only with a change).
description string Supporting text at the bottom.
trend readonly (number | null)[] Trend values rendered as a Sparkline (null is a gap); an empty array shows none.
trendLabel string Sparkline accessible label. Default: the card label.
trendColor SparklineColor 1 Chart palette slot of the sparkline (1 | 2 | 3 | 4 | 5 | 6 | 7 | 8; SparklineColor from @absoluteui/components/sparkline).
valueFormat Intl.NumberFormatOptions Number format of the value (and of an absolute change).
formatValue (value: number) => string Custom value formatter; wins over valueFormat (also used for an absolute change).
icon IconRef Decorative icon before the label.
href string Make the whole card a link (http(s) or relative URLs only; others throw). The URL can change with update(), but adding or removing href throws. Set at creation only.
busy boolean false Show ‘Loading’ instead of the value (no change) and set aria-busy.
sentiment 'higher-is-better' | 'lower-is-better' | 'neutral' "higher-is-better" Whether an increase is favorable, unfavorable, or neither; decides the color and the spoken ‘favorable’/‘unfavorable’. Globally configurable.
deltaFormat 'percent' | 'absolute' "percent" Relative change (percent, max 1 fraction digit) or the difference in value units. Globally configurable.
locale string "en-US" Number formatting and message locale. Globally configurable. Inherited from common configuration.
direction 'ltr' | 'rtl' "ltr" Passed to the trend sparkline. Globally configurable. Inherited from common configuration.
Property Type Description
host HTMLElement Consumer host; its children are owned by the component.
delta number | null The change as displayed (ratio or units), or null.
Method Description
update(options: Partial<StatCardOptions>): void Merge, validate, and re-render. Adding or removing href throws.
destroy(): void Destroy the sparkline, remove the rendering, the host class, and the configuration subscription.

All rules live in the aui.components cascade layer, so application styles override them without specificity battles.

Class Purpose
aui-stat-card Host.
aui-stat-card__body Card root: a div (role=group) or, with href, an a element (also aui-stat-card__link).
aui-stat-card__top | __icon | __label Label row, icon, label.
aui-stat-card__value Headline value (tabular numerals).
aui-stat-card__change | __delta | __arrow | __comparison Change row, change amount, arrow icon, comparison caption.
aui-stat-card__delta--good | --bad | --neutral Favorable, unfavorable, or neutral/no change.
aui-stat-card__sr Visually hidden spelled-out change for assistive technology.
aui-stat-card__trend Sparkline host.
aui-stat-card__description Supporting text.
Slot Default icon
icon (option) none
increase arrow arrow-up
decrease arrow arrow-down

Tokens this stylesheet reads (verified in CI). Override them in a theme or with the Theme Designer.

--aui-border-width | --aui-color-border | --aui-color-border-strong | --aui-color-error-fg | --aui-color-focus-ring | --aui-color-hover | --aui-color-success-fg | --aui-color-surface | --aui-color-text | --aui-color-text-muted | --aui-focus-ring-offset | --aui-focus-ring-width | --aui-font-family-sans | --aui-font-size-sm | --aui-font-size-xl | --aui-font-weight-medium | --aui-font-weight-semibold | --aui-line-height-tight | --aui-radius-lg | --aui-space-1 | --aui-space-2 | --aui-space-inset

type StatCardSentiment = 'higher-is-better' | 'lower-is-better' | 'neutral';
type StatCardDeltaFormat = 'percent' | 'absolute';
interface StatCardConfig { sentiment: StatCardSentiment; deltaFormat: StatCardDeltaFormat }

Pattern: Static content grouped with role=group and named by its visible label; with href, a single native link wrapping the whole card. The change is spelled out in visually hidden text; the visible signed amount is aria-hidden so it is not read twice.

Key Context Action
Tab / Shift+Tab Card with href The card is one tab stop (the link).
Enter Card link focused Follows the link (native).
Tab / Shift+Tab Card without href Not focusable; focus passes by.
Element Role
card root (no href) group, named by the label (aria-labelledby)
card root (href) link (native <a href>), named by its content
icon, arrow none (decorative icons)
trend Sparkline (see sparkline accessibility metadata)
Attribute Element Value
aria-labelledby card root (no href) id of the label element
aria-busy card root “true” while busy
aria-hidden visible signed change amount “true”; the visually hidden text reads ‘{increase|decrease|no change} of {amount}[, favorable|unfavorable]’

A linked card shows a focus-ring-width outline in color-focus-ring with focus-ring-offset. A plain card is not focusable.

Criterion Level Note
1.1.1 Non-text Content A
1.3.1 Info and Relationships A
1.4.1 Use of Color A
1.4.3 Contrast (Minimum) AA
2.4.4 Link Purpose (In Context) A
2.4.7 Focus Visible AA
4.1.2 Name, Role, Value A

Automated test results: Accessibility and compliance.

  • Favorable/unfavorable is conveyed by text (‘increase of 20%, favorable’; the sign is spoken as the direction word) and by the arrow direction, not by color alone (WCAG 1.4.1).
  • Sentiment ‘neutral’ and a zero change omit the favorable/unfavorable wording.
  • While busy the value reads ‘Loading’ and the change is not shown.
  • Manual screen-reader verification (NVDA, JAWS, VoiceOver) is pending.
  • A linked card’s accessible name is its whole text content (label, value, spelled-out change, caption, and description), which can be long.

default, busy, no value, increase/decrease x favorable/unfavorable, no change or neutral, link hover, link focus-visible.

From Trigger To
busy update({ busy: false, value }) value shown; change computed
any update({ value, previous }) re-rendered; change recomputed
any sentiment changes (option or global) favorable/unfavorable wording and color flip
  • Meaning never depends on color (WCAG 1.4.1): direction and favorability are text; the arrow shows direction.
  • neutral sentiment and zero changes omit “favorable”/“unfavorable”.
  • A linked card’s name is its whole text, which can be long; keep descriptions short.
  • Manual screen-reader verification is pending.

Fills its container (block-size: 100%, so it fills a Dashboard Grid cell). Text wraps; numbers use tabular figures. The sparkline spans the card width at 2rem height.

Logical properties; the sparkline receives the resolved direction. Intl formats numbers and signs for the locale.

Favorable changes use color-success-fg, unfavorable color-error-fg, neutral color-text. Forced colors: CanvasText for the border and all change colors.

configureAbsoluteUI({ components: { statCard: { deltaFormat: 'absolute', sentiment: 'neutral' } } });

Global options: sentiment, deltaFormat, plus inherited locale and direction. Changes apply to existing cards immediately.

Instance options override global and theme defaults. All options are dynamic, except that href cannot be added or removed after create.

icon (any IconRef, decorative). The change arrow uses arrow-up / arrow-down.

All text uses textContent. href must pass the URL policy (http, https, or scheme-less relative); javascript:, data:, and other schemes throw STAT_CARD_OPTIONS. CSP-safe.

No persistence, network access, or telemetry. Formatting happens locally.

Each render rebuilds a few spans and updates the sparkline in place. One configuration subscription per card. Budget: StatCard.create < 2 ms in headless Chromium (e2e performance test).

Tens of cards per dashboard. Trend arrays follow the Sparkline limits.

  • previous: 0 with percent: no change is shown (division by zero); pass delta.
  • A string value never computes a change; pass delta.
  • With formatValue and absolute, positive changes get a “+” prefix; negative ones rely on formatValue for the minus sign.
  • comparisonLabel shows only when there is a change.
  • An empty trend removes the sparkline.
Code Category Cause
STAT_CARD_OPTIONS configuration Empty label; invalid value, non-finite previous/delta, non-string texts, invalid trend, unsafe href, non-boolean busy, invalid valueFormat or formatValue; adding/removing href in update().
  • The trend needs the Sparkline stylesheet (@absoluteui/components/sparkline/style.css) too.
  • href presence is fixed at create time.
  • Manual screen-reader verification is pending.
  • HTML in texts.
  • Controls inside the card.
  • Several values per card.