Stat tile

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

Bereit für Prüfung9nächste Prüfung 14.11.
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

Punkte diesen Monat145Silber · Platz 2
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

Trainingsbesuche 202612’480+8 % gegenüber 2025
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.

FieldTypeRequiredDefaultDescription
labelstringrequired—What is counted ("Offene Anmeldungen").
min 1 chars
valuestring | numberrequired—The number. A number is formatted with Intl.NumberFormat(locale); a string is shown as given (already formatted, may include units: "CHF 1’240.–").
metastringoptional—Context line ("2 seit gestern", "nächste Prüfung 14.11.").
hrefstringoptional—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.
barebooleanoptionalfalseRender only the .stat block without the card wrapper (ignores href and variant).
localestringoptional"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

PartDescription
.stat__labelWhat is counted (muted, small, medium weight; may start with a 16 px icon).
.stat__valueThe number (3xl, bold, tabular figures).
.stat__metaContext line.
.card / .card[data-interactive]Wrapper (unless bare).

States

StateDescription
.card[data-variant="accent"] .stat__label/.stat__metaHero 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

DoLink the tile to the filtered list that resolves it.
DoPut tiles in a .grid with --grid-min ≈ 10–13rem.
DoFormat numbers with the club's locale (Swiss apostrophe "1’240").
Don'tDon't show vanity totals that lead nowhere.
Don'tDon't colour whole tiles by status; use the meta line or a badge.
Don'tDon't compare minors with each other in tiles (UX guide §13).

Related: Card · Progress bar · Progress ring