Stat
One metric, drawn as a description list rather than as a stack of divs: the group renders a `<dl>`, every card a `<dt>` and its `<dd>`s. The pairing a screen reader needs falls out of that markup for free, with no ARIA anywhere — and the same nesting is what lets `joined` move the whole box from the cards up to the group without moving a single element.
- Stable
- React 19
- Zero JS
- Semantic dl
- Orders completed
- 1,284
- +3.1%
- vs. last week
- Revenue
- 48.2K
- +1.8%
- MXN, month to date
- Returns
- 37
- −2.4%
- vs. last week
- Response time
- 1.4s
- 0.0%
- 24 h average
- Orders completed
- 1,284
- vs. last week
Code
<StatGroup><Stat label="Orders completed" value="1,284" caption="vs. last week" /></StatGroup><dl class="wayra-stat-group"><div class="wayra-stat" data-color="primary"> <dt class="wayra-stat-label">Orders completed</dt> <dd class="wayra-stat-value">1,284</dd> <dd class="wayra-stat-caption">vs. last week</dd></div></dl>label and value are the only required props, and the group is not optional — a Stat
renders a <dt> and one or more <dd>s, and both are only valid inside a <dl>. Rendering
one on its own is invalid HTML, and the parser will not repair it into anything useful.
The anatomy is the accessibility
Section titled “The anatomy is the accessibility”<dl class="wayra-stat-group"> <div class="wayra-stat" data-color="primary"> <dt class="wayra-stat-label"> <span class="wayra-stat-figure" aria-hidden="true">…</span>Orders completed </dt> <dd class="wayra-stat-value">1,284<span class="wayra-stat-unit">K</span></dd> <dd class="wayra-stat-delta-row"> <span class="wayra-stat-delta" data-trend="up"> <span class="wayra-stat-arrow" aria-hidden="true">▲</span>+3.1% </span> </dd> <dd class="wayra-stat-caption">vs. last week</dd> </div></dl>A <div> wrapping a <dt> and its <dd>s is explicitly allowed inside a <dl>, which is
what makes the card a real box while the pair stays a pair. The payoff is that the label is
the term and every other slot is a definition of it — a screen reader reads “Orders
completed, 1,284, +3.1%, vs. last week” as one group, and nothing in the component had to
say so with an aria-labelledby or a role.
Two consequences worth knowing:
- The UA indents every
<dd>. The stylesheet zeroes that margin on all four of them, because the card owns its own padding. - The figure rides inside the
<dt>, because a<div>in a<dl>may hold nothing but<dt>s followed by<dd>s. It isaria-hidden, so the term still reads as the label alone, and it is positioned out of flow, so it never lands between the label and its value.
Value, unit and caption
Section titled “Value, unit and caption”- Revenue
- 48.2K
- MXN, month to date
- Response time
- 1.4s
- 24 h average
Code
<StatGroup><Stat label="Revenue" value="48.2" unit="K" caption="MXN, month to date" /><Stat color="neutral" label="Response time" value="1.4" unit="s" caption="24 h average" /></StatGroup><dl class="wayra-stat-group"><div class="wayra-stat" data-color="primary"> <dt class="wayra-stat-label">Revenue</dt> <dd class="wayra-stat-value">48.2<span class="wayra-stat-unit">K</span></dd> <dd class="wayra-stat-caption">MXN, month to date</dd></div><div class="wayra-stat" data-color="neutral"> <dt class="wayra-stat-label">Response time</dt> <dd class="wayra-stat-value">1.4<span class="wayra-stat-unit">s</span></dd> <dd class="wayra-stat-caption">24 h average</dd></div></dl>unit is a <span> inside the value’s own <dd>, at 0.55em of it — a fraction, so it
tracks the value at every step including hero. It stays inside the same definition on
purpose: 48.2 and K are one number, and splitting them into two <dd>s would announce
them as two.
caption is the line under everything: the period, the source, the comparison. Both it and
the label are painted in the muted mix — base-content 63% against base-100 — which is
the same grey the placeholders and hints in the field components use.
Delta and trend
Section titled “Delta and trend”- Orders
- 1,284
- +3.1%
- Returns
- 37
- −2.4%
- Response time
- 1.4s
- 0.0%
- Sessions
- 9,412
- no comparison
Code
<Stat label="Orders" value="1,284" delta="+3.1%" trend="up" /><Stat label="Returns" value="37" delta="−2.4%" trend="down" /><Stat label="Response time" value="1.4" unit="s" delta="0.0%" trend="flat" /><Stat label="Sessions" value="9,412" delta="no comparison" /><dl class="wayra-stat-group"><div class="wayra-stat" data-color="primary"> <dt class="wayra-stat-label">Orders</dt> <dd class="wayra-stat-value">1,284</dd> <dd class="wayra-stat-delta-row"><span class="wayra-stat-delta" data-trend="up"><span class="wayra-stat-arrow" aria-hidden="true">▲</span>+3.1%</span></dd></div><div class="wayra-stat" data-color="primary"> <dt class="wayra-stat-label">Returns</dt> <dd class="wayra-stat-value">37</dd> <dd class="wayra-stat-delta-row"><span class="wayra-stat-delta" data-trend="down"><span class="wayra-stat-arrow" aria-hidden="true">▼</span>−2.4%</span></dd></div><div class="wayra-stat" data-color="primary"> <dt class="wayra-stat-label">Response time</dt> <dd class="wayra-stat-value">1.4<span class="wayra-stat-unit">s</span></dd> <dd class="wayra-stat-delta-row"><span class="wayra-stat-delta" data-trend="flat"><span class="wayra-stat-arrow" aria-hidden="true">→</span>0.0%</span></dd></div><div class="wayra-stat" data-color="primary"> <dt class="wayra-stat-label">Sessions</dt> <dd class="wayra-stat-value">9,412</dd> <dd class="wayra-stat-delta-row"><span class="wayra-stat-delta">no comparison</span></dd></div></dl>delta draws the capsule; trend only colours it and prints the arrow beside it. up is
green, down is red, flat and a missing trend are neutral — and the delta capsule is
the one part of the card that ignores color entirely. A rise is green whatever accent the
card carries, because a rise being “on brand” is not information.
The sign is yours to write. trend="down" with delta="2.4%" prints a red capsule and
a ▼ next to a number that says it went up by 2.4%. Colour dies in greyscale and the arrow
dies in a flattened palette; the text is the copy that survives both, so put the sign in it.
The arrow is real markup — a <span class="wayra-stat-arrow"> with the glyph in it — and
not a CSS content. A pseudo-element would vanish under Windows high contrast rules that
flatten backgrounds, and a screen reader would never see it either way. It is wrapped in
aria-hidden, so what gets announced is +3.1% and not “black up-pointing triangle”.
The figure, and the two layouts
Section titled “The figure, and the two layouts”- Orders completed
- 1,284
- +3.1%
- Response time
- 1.4s
- 24 h average
- No figure at all
- 0
Code
<Stat label="Orders completed" value="1,284" delta="+3.1%" trend="up" figure={<CartIcon />} /><Stat color="warning" layout="horizontal" label="Response time" value="1.4" unit="s" caption="24 h average" figure={<ClockIcon />} /><Stat color="neutral" layout="horizontal" label="No figure at all" value="0" /><dl class="wayra-stat-group"><div class="wayra-stat" data-color="primary"> <dt class="wayra-stat-label"><span class="wayra-stat-figure" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 4h2l2.3 10.2A2 2 0 0 0 9.3 16H18a2 2 0 0 0 2-1.6L21.5 7H6"/><circle cx="9.5" cy="20" r="1.3"/><circle cx="17.5" cy="20" r="1.3"/></svg></span>Orders completed</dt> <dd class="wayra-stat-value">1,284</dd> <dd class="wayra-stat-delta-row"><span class="wayra-stat-delta" data-trend="up"><span class="wayra-stat-arrow" aria-hidden="true">▲</span>+3.1%</span></dd></div><div class="wayra-stat" data-color="warning" data-layout="horizontal"> <dt class="wayra-stat-label"><span class="wayra-stat-figure" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="9"/><path d="M12 7v5.5l3.5 2"/></svg></span>Response time</dt> <dd class="wayra-stat-value">1.4<span class="wayra-stat-unit">s</span></dd> <dd class="wayra-stat-caption">24 h average</dd></div><div class="wayra-stat" data-color="neutral" data-layout="horizontal"> <dt class="wayra-stat-label">No figure at all</dt> <dd class="wayra-stat-value">0</dd></div></dl>figure is a tile in the card’s accent — the role washed to 14% behind an icon in the role
itself — and it is aria-hidden, so whatever it depicts has to be in the text as well.
It is out of flow in both layouts. stacked pins it to the trailing corner, horizontal
centres it on the leading edge and makes it larger; only the anchor and the box change, so
the text rows never have to know whether a figure showed up. What reserves room for it is a
:has() on the card:
.wayra-stat:has(> .wayra-stat-label > .wayra-stat-figure) { padding-inline-end: var(--_wayra-stat-figure-gutter);}That is why the third card above, which is horizontal with no figure, keeps its text on
the normal padding edge instead of leaving an empty column. The component writes
data-layout for both values, so the DOM says what the props say; stacked is what the
base rule already draws, so hand-written markup may leave the attribute off.
Display type
Section titled “Display type”The card’s geometry rides --wayra-size-field, so a theme that tightens the controls
tightens the padding here too. Its type does not: the four text steps are absolute,
because a metric card’s display type is not on the control ladder — a value that shrank with
the buttons around it would stop being the thing you read first.
| Part | Size | Notes |
|---|---|---|
label |
0.8rem |
font-weight: 500, letter-spacing: -0.006em — the display pair shared with a List title. |
value |
1.7rem, 2.4rem hero |
font-weight: 600, letter-spacing: -0.02em, line-height: 1.1. |
unit |
0.55em of the value |
Relative, so it follows hero. |
delta |
0.78rem |
Tabular as well: a column of deltas lines up too. |
caption |
0.75rem |
Muted. |
The value carries font-variant-numeric: tabular-nums, and it is not cosmetic. Proportional
digits give 1 a narrower advance than 8, so a column of cards showing 1,284, 9,412
and 48.2 puts its digits in three different places and the eye has to re-find the number
in every card. Tabular figures make the column an actual column. The same rule is why the
delta capsule sets it too, and why the numeric cells in Table do.
- Quarterly revenue
- 1.24M
- +8.6%
- MXN
- New customers
- 312
- +4.2%
Code
<Stat emphasis="hero" label="Quarterly revenue" value="1.24" unit="M" delta="+8.6%" trend="up" caption="MXN" /><Stat color="secondary" label="New customers" value="312" delta="+4.2%" trend="up" /><dl class="wayra-stat-group"><div class="wayra-stat" data-color="primary" data-emphasis="hero"> <dt class="wayra-stat-label">Quarterly revenue</dt> <dd class="wayra-stat-value">1.24<span class="wayra-stat-unit">M</span></dd> <dd class="wayra-stat-delta-row"><span class="wayra-stat-delta" data-trend="up"><span class="wayra-stat-arrow" aria-hidden="true">▲</span>+8.6%</span></dd> <dd class="wayra-stat-caption">MXN</dd></div><div class="wayra-stat" data-color="secondary"> <dt class="wayra-stat-label">New customers</dt> <dd class="wayra-stat-value">312</dd> <dd class="wayra-stat-delta-row"><span class="wayra-stat-delta" data-trend="up"><span class="wayra-stat-arrow" aria-hidden="true">▲</span>+4.2%</span></dd></div></dl>emphasis="hero" is the one step above: it prints the value at 2.4rem and changes nothing
else. It is for the number a panel leads with, which means one per panel.
A joined strip
Section titled “A joined strip”- Quarterly revenue
- 1.24M
- +8.6%
- MXN
- New customers
- 312
- +4.2%
- Average ticket
- 3,980MXN
Code
<StatGroup joined aria-label="Quarter summary"><Stat emphasis="hero" label="Quarterly revenue" value="1.24" unit="M" delta="+8.6%" trend="up" caption="MXN" /><Stat color="secondary" label="New customers" value="312" delta="+4.2%" trend="up" /><Stat color="secondary" label="Average ticket" value="3,980" unit="MXN" /></StatGroup><dl class="wayra-stat-group" data-joined="true" tabindex="0" aria-label="Quarter summary"><div class="wayra-stat" data-color="primary" data-emphasis="hero"> <dt class="wayra-stat-label">Quarterly revenue</dt> <dd class="wayra-stat-value">1.24<span class="wayra-stat-unit">M</span></dd> <dd class="wayra-stat-delta-row"><span class="wayra-stat-delta" data-trend="up"><span class="wayra-stat-arrow" aria-hidden="true">▲</span>+8.6%</span></dd> <dd class="wayra-stat-caption">MXN</dd></div><div class="wayra-stat" data-color="secondary"> <dt class="wayra-stat-label">New customers</dt> <dd class="wayra-stat-value">312</dd> <dd class="wayra-stat-delta-row"><span class="wayra-stat-delta" data-trend="up"><span class="wayra-stat-arrow" aria-hidden="true">▲</span>+4.2%</span></dd></div><div class="wayra-stat" data-color="secondary"> <dt class="wayra-stat-label">Average ticket</dt> <dd class="wayra-stat-value">3,980<span class="wayra-stat-unit">MXN</span></dd></div></dl>joined moves the box up: the group takes the border, the corner and the surface, and
its members give up all three, keeping only a hairline between them. One attribute
re-points three rules.
The layout changes with it. The resting group is grid-template-columns: repeat(auto-fit, minmax(var(--wayra-stat-min), 1fr)), so cards wrap as soon as a column would fall under
11rem. A joined group switches to grid-auto-flow: column with overflow-x: auto: on a
narrow screen the strip scrolls rather than reflowing, because a panel divided by
hairlines that wraps onto a second line stops reading as one panel.
A scroll box the keyboard cannot focus cannot be scrolled from the keyboard either, so a
joined group is a tab stop: the component writes tabindex="0", and the strip rings from
the inside, the way Table’s scroll region does. Name it with aria-label or
aria-labelledby, as on the example above. It takes no role — on a <dl>, any role but
list or none would break the term/definition pairing the whole component rests on.
Loading
Section titled “Loading”- Orders completed
- 1,284
- +3.1%
- vs. last week
Code
<Stat loading label="Orders completed" value="1,284" delta="+3.1%" trend="up" caption="vs. last week" figure={<CartIcon />} /><dl class="wayra-stat-group"><div class="wayra-stat" data-color="primary" aria-busy="true"> <dt class="wayra-stat-label"><span class="wayra-stat-figure" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 4h2l2.3 10.2A2 2 0 0 0 9.3 16H18a2 2 0 0 0 2-1.6L21.5 7H6"/><circle cx="9.5" cy="20" r="1.3"/><circle cx="17.5" cy="20" r="1.3"/></svg></span>Orders completed</dt> <dd class="wayra-stat-value">1,284</dd> <dd class="wayra-stat-delta-row"><span class="wayra-stat-delta" data-trend="up"><span class="wayra-stat-arrow" aria-hidden="true">▲</span>+3.1%</span></dd> <dd class="wayra-stat-caption">vs. last week</dd></div></dl>loading marks the card aria-busy and replaces the ink of the value and the delta with a
breathing bar. The text is still there — only color: transparent and a wash behind it —
which is what makes each bar exactly as wide as the number it stands in for, so nothing
jumps when the data lands.
The label and the caption stay legible on purpose: they are known before the number is, and
a card whose every row is a grey bar tells you nothing about what is loading. Under
prefers-reduced-motion: reduce the pulse stops and the bars keep their fill, the way
Skeleton rests.
Composition, not more props
Section titled “Composition, not more props”- Sessions
- 9,412
- +12.7%
- last 7 days
- Monthly target
- 62%
- 18 days left
Code
<Stat color="info" label="Sessions" value="9,412" delta="+12.7%" trend="up" caption="last 7 days"><svg className="wayra-stat-spark" viewBox="0 0 100 32" preserveAspectRatio="none" aria-hidden="true"> <path className="wayra-stat-spark-area" d="M0 26 20 18 40 21 60 11 80 14 100 4 100 32 0 32Z" /> <path d="M0 26 20 18 40 21 60 11 80 14 100 4" vectorEffect="non-scaling-stroke" /></svg></Stat>
<Stat color="success" label="Monthly target" value="62" unit="%" caption="18 days left" figure={<RadialProgress color="success" size="xs" value={62} aria-hidden />}><Progress color="success" value={62} max={100} aria-label="Monthly target" /></Stat><dl class="wayra-stat-group"><div class="wayra-stat" data-color="info"> <dt class="wayra-stat-label">Sessions</dt> <dd class="wayra-stat-value">9,412</dd> <dd class="wayra-stat-delta-row"><span class="wayra-stat-delta" data-trend="up"><span class="wayra-stat-arrow" aria-hidden="true">▲</span>+12.7%</span></dd> <dd class="wayra-stat-caption">last 7 days</dd> <dd class="wayra-stat-extra"><svg class="wayra-stat-spark" viewBox="0 0 100 32" preserveAspectRatio="none" aria-hidden="true"><path class="wayra-stat-spark-area" d="M0 26 20 18 40 21 60 11 80 14 100 4 100 32 0 32Z"/><path d="M0 26 20 18 40 21 60 11 80 14 100 4" vector-effect="non-scaling-stroke"/></svg></dd></div><div class="wayra-stat" data-color="success"> <dt class="wayra-stat-label"><span class="wayra-stat-figure" aria-hidden="true"><div class="wayra-radial-progress" role="progressbar" data-color="success" data-size="xs" aria-valuenow="62" aria-valuemin="0" aria-valuemax="100" aria-hidden="true" style="--wayra-radial-progress-value: 62"><div class="wayra-radial-progress-ring" aria-hidden="true"></div></div></span>Monthly target</dt> <dd class="wayra-stat-value">62<span class="wayra-stat-unit">%</span></dd> <dd class="wayra-stat-caption">18 days left</dd> <dd class="wayra-stat-extra"><progress class="wayra-progress" data-color="success" data-size="md" value="62" max="100" aria-label="Monthly target"></progress></dd></div></dl>children land in a <dd class="wayra-stat-extra"> under the caption, and that is where a
trend line, a bar or anything else with its own shape goes. Stat draws no ring and
no bar of its own: RadialProgress and
Progress already carry their own value semantics, and a second copy
inside this component would be a second thing to keep in step with them.
The sparkline is the same deal in the other direction — the library draws none, it only
dresses one. Give your own inline <svg> the class wayra-stat-spark and it gets the
height, the top margin and the accent stroke; add wayra-stat-spark-area to a second path
and it gets the 12% wash under the line.
.wayra-stat-spark > path { fill: none; stroke: var(--wayra-tone); stroke-width: 2;}Because the stroke is set in CSS and the viewBox is stretched by
preserveAspectRatio="none", the line would thicken horizontally and thin vertically as the
card grows. vector-effect="non-scaling-stroke" on the path is the one part of that CSS
cannot fix — it belongs on your markup, and every example here carries it.
Flat, not raised
Section titled “Flat, not raised”The card is flat and opaque, like every surface in the library: a base-100
background and a hairline border of base-content at 14%, with no shadow, no lit edge and
no blur. The border alone is what separates it from the page. Translucency was withdrawn in
August 2026, and the relief that briefly replaced it — a lit top edge over a contact
shadow — was withdrawn from the overlays the same month and from Stat in September.
The border colour is declared on both roots — :is(.wayra-stat-group, .wayra-stat) —
for one reason: joined moves the box from the cards to the group, and the group needs the
same line available when it does.
The capsule spends the contrast margin
Section titled “The capsule spends the contrast margin”The colour contract promises a role reads at 4.5:1 against a clean base-100. The delta
capsule does not sit on a clean base-100 — it sits on a wash of its own role, and the wash
eats the margin. Measured on the default theme, the raw token on its own capsule reads
3.75:1 for up and 3.80:1 for down: below AA, on text small enough to need it.
Settling the role 20% toward base-content buys it back without losing the hue:
.wayra-stat-delta[data-trend="up"] { --_wayra-stat-delta-color: color-mix(in oklch, var(--wayra-color-success) 80%, var(--wayra-color-base-content)); --_wayra-stat-delta-wash: color-mix(in oklch, var(--wayra-color-success) 14%, transparent);}That lands at 4.90:1 (up) and 5.31:1 (down) in light, 8.87:1 and 6.46:1
in dark. Alert records the same shortfall for a title
over its soft wash and resolves it the other way — by taking the colour off the text
entirely — which is the honest comparison: two components, one arithmetic, two different
calls about what the colour is worth.
Styling contract
Section titled “Styling contract”The class plus the data-* attributes is the public API, and every preview on this page is
hand-authored HTML with no React in it. The nesting is part of the contract: a <dl> for
the group, a <div> per card, a <dt> and its <dd>s inside.
| Attribute | On | Values |
|---|---|---|
data-color |
.wayra-stat |
The eight roles. Always written; primary when unset. |
data-layout |
.wayra-stat |
stacked, horizontal. Always written; absent means stacked. |
data-emphasis |
.wayra-stat |
hero only. |
aria-busy |
.wayra-stat |
true while loading. |
data-trend |
.wayra-stat-delta |
up, down, flat. Absent is neutral. |
data-joined |
.wayra-stat-group |
true only. Hand-written, add tabindex="0" and a name with it. |
Two custom properties are meant to be overridden, per instance or per theme:
| Property | Default | What it does |
|---|---|---|
--wayra-stat-min |
11rem |
The narrowest a column may get before the grid wraps, and the minimum width of a member in a joined strip. A content measure, so it stays absolute. |
--wayra-stat-pad |
calc(var(--wayra-size-field) * 4.4) |
The card’s inner air, and the figure’s inset from both edges. |
API reference
Section titled “API reference”StatProps extends ComponentProps<"div"> minus color, so id, style, ref,
className and every data-* land on the card.
| Prop | Type | Default | Description |
|---|---|---|---|
label |
ReactNode |
— (required) | What the number measures. Rendered as the <dt> of the pair. |
value |
ReactNode |
— (required) | The number itself, set in tabular figures. |
unit |
ReactNode |
— | Suffix beside the value at a smaller step: %, K, MXN. |
delta |
ReactNode |
— | Change since the last period, in a capsule under the value. Include the sign. |
trend |
"up" | "down" | "flat" |
— | Colours the capsule and prints the arrow. Means nothing without delta. |
caption |
ReactNode |
— | A line under the delta: the period, the source, the comparison. |
figure |
ReactNode |
— | Corner decoration — an icon or a composed ring. aria-hidden. |
children |
ReactNode |
— | Extra content in a <dd> of its own: a sparkline, a Progress. |
color |
WayraColor |
"primary" |
Role behind the figure tile and the sparkline stroke. The delta capsule ignores it. |
layout |
"stacked" | "horizontal" |
"stacked" |
Where the figure sits. Emitted as data-layout. |
emphasis |
"hero" |
— | Prints the value one step larger. |
loading |
boolean |
false |
Bars for the value and the delta, plus aria-busy. |
unstyled |
boolean |
false |
Drops the card’s class and every part class under it. The data-* stay. |
StatGroupProps extends ComponentProps<"dl">.
| Prop | Type | Default | Description |
|---|---|---|---|
joined |
boolean |
false |
Draws the group as one panel and strips the members of their box. The strip scrolls, so it is a tab stop: name it with aria-label. |
children |
ReactNode |
— (required) | The stats. |
unstyled |
boolean |
false |
Drops wayra-stat-group from the <dl>. Per instance: a card inside an unstyled group keeps its own class unless it opts out too. |
Accessibility
Section titled “Accessibility”- The markup is the accessibility. A
<dl>of<dt>/<dd>pairs gives the label-value relationship for free. There is norole, noaria-labelledbyand noaria-describedbyanywhere in this component, and that is the point: nothing can drift out of step with the structure. - A
Statoutside aStatGroupis broken markup.<dt>and<dd>outside a<dl>have no defined pairing, and no ARIA on top will restore it. - A joined strip is a tab stop. It scrolls sideways, and a scroll box the keyboard
cannot focus cannot be scrolled from it (axe
scrollable-region-focusable). The<dl>takestabindex="0"and no role, so it keeps its pairing; its name is thearia-labelyou give it. - The delta text carries its own meaning. The capsule’s colour and the arrow are
redundant signals — one dies in greyscale, the other in a flattened palette. Write the
sign into
delta. - The arrow is hidden, not absent. It is real markup inside an
aria-hiddenspan, so it survives Windows high contrast visually and is never spelled out audibly. - The figure is hidden. Anything it depicts must also be in the text. A composed
RadialProgressin there is hidden with it, which is why the same 62% appears as the card’s ownvalue. - Loading announces once, on the card.
aria-busygoes on the<div>, not on each bar — one wait, one announcement. The bars themselves are the real text with its ink dropped, so nothing is removed from the accessibility tree while it loads. - Contrast. Label and caption are the muted mix (
base-content63% onbase-100); the delta capsule is measured above at 4.90–8.87:1 across the schemes. The card’s border is a 14% mix ofbase-content— decorative, and outside 1.4.11, which covers boundaries that identify a control. - Forced colors. Backgrounds flatten to one system colour, so the card and a joined
panel redraw their border in
CanvasTextand the loading bars take an outline. The capsule needs no rescue: its arrow is real markup and its text is real text.
Do and don’t
Section titled “Do and don’t”Do
- Returns
- 37
- −2.4%
- vs. last week
The sign is in the text. Greyscale, high contrast and a screen reader all still get the direction.
Don't
- Returns
- 37
- 2.4%
- vs. last week
Red and a ▼ carry the whole message, and the number reads as a rise to anyone who receives neither.
Do
- Orders
- 1,284
- Revenue
- 48.2K
Both cards inside the group's : every is paired with its own s.
Don't
A card on its own. A and a with no list around them are invalid, and the pairing is gone.