Data displaystable.stat
Stat tile
One number with a label and a context line, usually leading to where you act on it ("Offene Anmeldungen: 4").
Stat tiles answer a question the role actually asks, and ideally link to where it is resolved: "Offene Anmeldungen 4 → list of open registrations". A number for its own sake ("Mitglieder: 312") is decoration — leave it out (UX guide §4.2).
value may be a number or a pre-formatted string. Numbers are formatted by render with Intl.NumberFormat in the given locale (default de-CH, e.g. 1240 → "1’240"). Pass a string when the value needs units, currency or rounding you control ("CHF 1’240.–", "92 %", "3 von 5").
By default the tile is wrapped in a .card (a link card when href is set). Set bare: true to render only the .stat block, e.g. inside an existing card or list.
The meta line gives context: a comparison ("2 seit gestern"), the next date ("nächste Prüfung 14.11.") or a rank ("Silber · Platz 2"). Write trends as words, not only arrows or colours.
Examples
Link tile
Data
render({
"label": "Offene Anmeldungen",
"value": 4,
"meta": "2 seit gestern",
"href": "#anmeldungen?status=offen"
})Markup
<a class="card" data-interactive href="#anmeldungen?status=offen"><div class="stat"><span class="stat__label">Offene Anmeldungen</span><span class="stat__value">4</span><span class="stat__meta">2 seit gestern</span></div></a>Static tile
Data
render({
"label": "Bereit für Prüfung",
"value": 9,
"meta": "nächste Prüfung 14.11."
})Markup
<div class="card"><div class="stat"><span class="stat__label">Bereit für Prüfung</span><span class="stat__value">9</span><span class="stat__meta">nächste Prüfung 14.11.</span></div></div>Accent hero tile
Data
render({
"label": "Punkte diesen Monat",
"value": 145,
"meta": "Silber · Platz 2",
"variant": "accent"
})Markup
<div class="card" data-variant="accent"><div class="stat"><span class="stat__label">Punkte diesen Monat</span><span class="stat__value">145</span><span class="stat__meta">Silber · Platz 2</span></div></div>Pre-formatted string
Data
render({
"label": "Offene Rechnungen",
"value": "CHF 1’240.–",
"meta": "7 Rechnungen, älteste vom 02.09.",
"href": "#rechnungen?status=offen"
})Markup
<a class="card" data-interactive href="#rechnungen?status=offen"><div class="stat"><span class="stat__label">Offene Rechnungen</span><span class="stat__value">CHF 1’240.–</span><span class="stat__meta">7 Rechnungen, älteste vom 02.09.</span></div></a>Large number, formatted
Data
render({
"label": "Trainingsbesuche 2026",
"value": 12480,
"meta": "+8 % gegenüber 2025"
})Markup
<div class="card"><div class="stat"><span class="stat__label">Trainingsbesuche 2026</span><span class="stat__value">12’480</span><span class="stat__meta">+8 % gegenüber 2025</span></div></div>Data contract
What render(data) accepts — validated against this JSON Schema in development and tests. Unknown fields are rejected. The same schema is in components.json.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
label | string | required | — | What is counted ("Offene Anmeldungen"). min 1 chars |
value | string | number | required | — | The number. A number is formatted with Intl.NumberFormat(locale); a string is shown as given (already formatted, may include units: "CHF 1’240.–"). |
meta | string | optional | — | Context line ("2 seit gestern", "nächste Prüfung 14.11."). |
href | string | optional | — | Destination where the number is acted on; the whole tile becomes a link card. |
variant | "default" | "accent" | "subtle" | optional | "default" | Card variant. accent (accent-subtle tint, design guide §9.3) only for the single hero tile of a screen. |
bare | boolean | optional | false | Render only the .stat block without the card wrapper (ignores href and variant). |
locale | string | optional | "de-CH" | Locale for formatting numeric values (de-CH, fr-CH, it-CH, en-GB).pattern ^[a-z]{2}(-[A-Z]{2})?$ |
JSON Schema
{
"type": "object",
"additionalProperties": false,
"required": [
"label",
"value"
],
"properties": {
"label": {
"type": "string",
"minLength": 1,
"description": "What is counted (\"Offene Anmeldungen\")."
},
"value": {
"type": [
"string",
"number"
],
"description": "The number. A number is formatted with `Intl.NumberFormat(locale)`; a string is shown as given (already formatted, may include units: \"CHF 1’240.–\")."
},
"meta": {
"type": "string",
"description": "Context line (\"2 seit gestern\", \"nächste Prüfung 14.11.\")."
},
"href": {
"type": "string",
"description": "Destination where the number is acted on; the whole tile becomes a link card."
},
"variant": {
"type": "string",
"enum": [
"default",
"accent",
"subtle"
],
"default": "default",
"description": "Card variant. `accent` (accent-subtle tint, design guide §9.3) only for the single hero tile of a screen."
},
"bare": {
"type": "boolean",
"default": false,
"description": "Render only the `.stat` block without the card wrapper (ignores `href` and `variant`)."
},
"locale": {
"type": "string",
"pattern": "^[a-z]{2}(-[A-Z]{2})?$",
"default": "de-CH",
"description": "Locale for formatting numeric values (`de-CH`, `fr-CH`, `it-CH`, `en-GB`)."
}
}
}Markup & states
Root: div (inside div.card or a.card[data-interactive]).stat — usable without render() by writing the markup directly.
Parts
| Part | Description |
|---|---|
.stat__label | What is counted (muted, small, medium weight; may start with a 16 px icon). |
.stat__value | The number (3xl, bold, tabular figures). |
.stat__meta | Context line. |
.card / .card[data-interactive] | Wrapper (unless bare). |
States
| State | Description |
|---|---|
.card[data-variant="accent"] .stat__label/.stat__meta | Hero tile: accent-subtle background; label, value and meta use the accent-subtle text colour. |
Accessibility
- Label comes before the value in the DOM, so a screen reader reads "Offene Anmeldungen, 4, 2 seit gestern" — also the accessible name of a link tile.
- Tabular figures keep columns of tiles aligned; values never rely on colour (a red number needs a word like "überfällig").
Guidance
.grid with --grid-min ≈ 10–13rem.Related: Card · Progress bar · Progress ring