Kinder 7–10 · Mo 17:30
Präsenz erfasst14 anwesend · 2 abwesend · Leitung: Sandra Keller
Data displaystable.card
A flat, bordered surface that groups one topic: a title with optional status, a few lines of text and up to three actions — or, as a whole, one link.
Cards group content about one thing: a training ("Kinder 7–10 · Mo 17:30"), the detail of a registration, a dashboard item. They are flat (border, no shadow) and sit in a .grid or .stack. Don't nest cards in cards; use list rows or a subtle card inside instead.
Two modes. Static (<article>): .card__header with title and optional status badge, .card__body with the muted meta line and body paragraphs, and an optional .card__footer with up to three actions (button data, primary last) above a divider. Interactive (href): the whole card is one <a class="card" data-interactive>, for dashboard items that lead somewhere; it must not contain other actions. On hover it shifts to the second surface with a stronger border — no shadow, in-flow content stays flat (design guide §6.2).
variant: "accent" tints the card with --color-accent-subtle (one hero card per screen at most; never an accent fill, design guide §2.4); subtle uses the second surface for secondary or nested panels.
Body content is plain text paragraphs. Richer content (lists, key-value lists, stat tiles) is composed by the app: render the card markup by hand or place other components inside a .card element — never pass HTML through data.
14 anwesend · 2 abwesend · Leitung: Sandra Keller
render({
"title": "Kinder 7–10 · Mo 17:30",
"badge": {
"label": "Präsenz erfasst",
"tone": "success"
},
"meta": "14 anwesend · 2 abwesend · Leitung: Sandra Keller",
"actions": [
{
"label": "Öffnen",
"href": "#training-4711"
}
]
})<article class="card"><header class="card__header"><h3 class="card__title">Kinder 7–10 · Mo 17:30</h3><span class="badge" data-status="success"><svg class="icon" data-size="sm" aria-hidden="true"><use href="../../packages/ui/icons/icons.svg#check-circle"/></svg>Präsenz erfasst</span></header><div class="card__body"><p class="u-muted">14 anwesend · 2 abwesend · Leitung: Sandra Keller</p></div><footer class="card__footer"><a class="button" href="#training-4711">Öffnen</a></footer></article>Gewünschte Gruppe: Kinder 7–10. Familientarif, weil die Schwester Nina bereits Mitglied ist.
Eingegangen gestern über das öffentliche Anmeldeformular.
render({
"id": "reg-17",
"title": "Luca Gerber",
"headingLevel": 2,
"badge": {
"label": "Wartet auf Freigabe",
"tone": "warning"
},
"body": [
"Gewünschte Gruppe: Kinder 7–10. Familientarif, weil die Schwester Nina bereits Mitglied ist.",
"Eingegangen gestern über das öffentliche Anmeldeformular."
],
"actions": [
{
"label": "Ablehnen",
"variant": "ghost"
},
{
"label": "Bearbeiten"
},
{
"label": "Freigeben",
"variant": "primary"
}
]
})<article class="card" id="reg-17" aria-labelledby="reg-17-title"><header class="card__header"><h2 class="card__title" id="reg-17-title">Luca Gerber</h2><span class="badge" data-status="warning"><svg class="icon" data-size="sm" aria-hidden="true"><use href="../../packages/ui/icons/icons.svg#alert-triangle"/></svg>Wartet auf Freigabe</span></header><div class="card__body"><p>Gewünschte Gruppe: Kinder 7–10. Familientarif, weil die Schwester Nina bereits Mitglied ist.</p><p>Eingegangen gestern über das öffentliche Anmeldeformular.</p></div><footer class="card__footer"><button class="button" type="button" data-variant="ghost">Ablehnen</button><button class="button" type="button">Bearbeiten</button><button class="button" type="button" data-variant="primary">Freigeben</button></footer></article>render({
"title": "Gurtprüfung 14.11.",
"meta": "38 Anmeldungen · 3 ohne Gebühr",
"href": "#pruefung-2026-11"
})<a class="card" data-interactive href="#pruefung-2026-11"><header class="card__header"><h3 class="card__title">Gurtprüfung 14.11.</h3></header><div class="card__body"><p class="u-muted">38 Anmeldungen · 3 ohne Gebühr</p></div></a>Heute 17:30 · Kinder 7–10 · Dojang Breitenrain
render({
"variant": "accent",
"title": "Nächstes Training",
"body": [
"Heute 17:30 · Kinder 7–10 · Dojang Breitenrain"
]
})<article class="card" data-variant="accent"><header class="card__header"><h3 class="card__title">Nächstes Training</h3></header><div class="card__body"><p>Heute 17:30 · Kinder 7–10 · Dojang Breitenrain</p></div></article>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 |
|---|---|---|---|---|
id | string | optional | — | Id of the card. When set together with title, the title gets {id}-title and the article is labelled by it (aria-labelledby).pattern ^[A-Za-z][\w-]*$ |
title | string | optional | — | Card title ("Kinder 7–10 · Mo 17:30"). min 1 chars |
headingLevel | integer | optional | 3 | Heading level of the title (h2–h4); pick the level that fits the page outline.≥ 2 · ≤ 4 |
badge | object | optional | — | Optional status shown at the end of the header (see Status badge). |
badge.label | string | required | — | Status text, e.g. "Wartet auf Freigabe", "Bezahlt", "Fehler in Webling". min 1 chars |
badge.tone | "neutral" | "info" | "success" | "warning" | "danger" | optional | "neutral" | Status, rendered as data-status + leading icon. neutral (draft, inactive; no icon), info (planned, new), success (done, paid), warning (waiting, needs attention), danger (failed, overdue). |
badge.title | string | optional | — | Optional tooltip with more detail ("seit 3 Tagen offen"). Not announced reliably; never put essential information only here. |
meta | string | optional | — | One muted line under the header ("14 anwesend · 2 abwesend · Leitung: Sandra Keller"). |
body | array of string | optional | — | Body paragraphs, plain text, in display order. |
variant | "default" | "accent" | "subtle" | optional | "default" | accent: accent-subtle hero tint (one hero card per screen); subtle: second surface for nested/secondary panels. |
href | string | optional | — | Makes the whole card one link (<a class="card" data-interactive href>). Cannot be combined with actions. |
actions | array of objects | optional | — | Buttons in the .card__footer (see Button). Put the primary action last. Not allowed together with href.max 3 items |
actions[].label | string | optional | — | Visible text. Required unless iconOnly.min 1 chars |
actions[].variant | "secondary" | "primary" | "ghost" | "danger" | "danger-ghost" | optional | "secondary" | Visual weight / meaning. |
actions[].size | "sm" | "md" | "lg" | optional | "md" | sm (36 px) only in dense desktop tables; lg (52 px) for the single main action of a screen. |
actions[].icon | string | optional | — | Icon id from the sprite, shown before the label. |
actions[].iconOnly | boolean | optional | false | Square button showing only icon. Requires ariaLabel. |
actions[].ariaLabel | string | optional | — | Accessible name when the visible label is missing or ambiguous ("Weitere Aktionen für Lea Müller"). |
actions[].href | string | optional | — | Render an <a> that looks like a button (navigation, not actions). |
actions[].type | "button" | "submit" | "reset" | optional | "button" | Button type inside forms. |
actions[].value | string | optional | — | Form value, e.g. cancel / confirm inside <form method="dialog">. |
actions[].name | string | optional | — | Form field name when the button submits a value. |
actions[].disabled | boolean | optional | false | Not available now. Prefer explaining why nearby over silently disabling. |
actions[].busy | boolean | optional | false | Action in progress (aria-busy). |
actions[].formNoValidate | boolean | optional | false | Submit without native validation — use on "Abbrechen" inside <form method="dialog"> so required fields do not block closing. |
actions[].block | "always" | "mobile" | optional | — | Full width always, or only below 768 px. |
actions[].behavior | string | optional | — | Behaviour hook (data-behavior), e.g. open-dialog, theme-toggle. |
actions[].action | string | optional | — | Screen action name (data-action). Declared in the actions of the screen with a handler and a risk class; the engine adds undo or confirm accordingly.pattern ^[a-z][a-zA-Z0-9-]*$ |
actions[].actionId | string | optional | — | Id of the thing the action applies to (data-id), when the button is not inside an element that has one.pattern ^[A-Za-z0-9_-]+$ |
actions[].target | string | optional | — | Id of the element a behaviour acts on (data-target), e.g. the dialog to open. |
actions[].popoverTarget | string | optional | — | Id of a popover menu this button toggles (popovertarget). |
{
"type": "object",
"additionalProperties": false,
"required": [],
"properties": {
"id": {
"type": "string",
"pattern": "^[A-Za-z][\\w-]*$",
"description": "Id of the card. When set together with `title`, the title gets `{id}-title` and the article is labelled by it (`aria-labelledby`)."
},
"title": {
"type": "string",
"minLength": 1,
"description": "Card title (\"Kinder 7–10 · Mo 17:30\")."
},
"headingLevel": {
"type": "integer",
"minimum": 2,
"maximum": 4,
"default": 3,
"description": "Heading level of the title (`h2`–`h4`); pick the level that fits the page outline."
},
"badge": {
"type": "object",
"additionalProperties": false,
"required": [
"label"
],
"properties": {
"label": {
"type": "string",
"minLength": 1,
"description": "Status text, e.g. \"Wartet auf Freigabe\", \"Bezahlt\", \"Fehler in Webling\"."
},
"tone": {
"type": "string",
"enum": [
"neutral",
"info",
"success",
"warning",
"danger"
],
"default": "neutral",
"description": "Status, rendered as `data-status` + leading icon. `neutral` (draft, inactive; no icon), `info` (planned, new), `success` (done, paid), `warning` (waiting, needs attention), `danger` (failed, overdue)."
},
"title": {
"type": "string",
"description": "Optional tooltip with more detail (\"seit 3 Tagen offen\"). Not announced reliably; never put essential information only here."
}
},
"description": "Optional status shown at the end of the header (see Status badge)."
},
"meta": {
"type": "string",
"description": "One muted line under the header (\"14 anwesend · 2 abwesend · Leitung: Sandra Keller\")."
},
"body": {
"type": "array",
"description": "Body paragraphs, plain text, in display order.",
"items": {
"type": "string",
"minLength": 1,
"description": "One paragraph."
}
},
"variant": {
"type": "string",
"enum": [
"default",
"accent",
"subtle"
],
"default": "default",
"description": "`accent`: accent-subtle hero tint (one hero card per screen); `subtle`: second surface for nested/secondary panels."
},
"href": {
"type": "string",
"description": "Makes the whole card one link (`<a class=\"card\" data-interactive href>`). Cannot be combined with `actions`."
},
"actions": {
"type": "array",
"maxItems": 3,
"description": "Buttons in the `.card__footer` (see Button). Put the primary action last. Not allowed together with `href`.",
"items": {
"type": "object",
"additionalProperties": false,
"required": [],
"properties": {
"label": {
"type": "string",
"minLength": 1,
"description": "Visible text. Required unless `iconOnly`."
},
"variant": {
"type": "string",
"enum": [
"secondary",
"primary",
"ghost",
"danger",
"danger-ghost"
],
"default": "secondary",
"description": "Visual weight / meaning."
},
"size": {
"type": "string",
"enum": [
"sm",
"md",
"lg"
],
"default": "md",
"description": "`sm` (36 px) only in dense desktop tables; `lg` (52 px) for the single main action of a screen."
},
"icon": {
"type": "string",
"description": "Icon id from the sprite, shown before the label."
},
"iconOnly": {
"type": "boolean",
"default": false,
"description": "Square button showing only `icon`. Requires `ariaLabel`."
},
"ariaLabel": {
"type": "string",
"description": "Accessible name when the visible label is missing or ambiguous (\"Weitere Aktionen für Lea Müller\")."
},
"href": {
"type": "string",
"description": "Render an `<a>` that looks like a button (navigation, not actions)."
},
"type": {
"type": "string",
"enum": [
"button",
"submit",
"reset"
],
"default": "button",
"description": "Button type inside forms."
},
"value": {
"type": "string",
"description": "Form value, e.g. `cancel` / `confirm` inside `<form method=\"dialog\">`."
},
"name": {
"type": "string",
"description": "Form field name when the button submits a value."
},
"disabled": {
"type": "boolean",
"default": false,
"description": "Not available now. Prefer explaining why nearby over silently disabling."
},
"busy": {
"type": "boolean",
"default": false,
"description": "Action in progress (`aria-busy`)."
},
"formNoValidate": {
"type": "boolean",
"default": false,
"description": "Submit without native validation — use on \"Abbrechen\" inside `<form method=\"dialog\">` so required fields do not block closing."
},
"block": {
"type": "string",
"enum": [
"always",
"mobile"
],
"description": "Full width always, or only below 768 px."
},
"behavior": {
"type": "string",
"description": "Behaviour hook (`data-behavior`), e.g. `open-dialog`, `theme-toggle`."
},
"action": {
"type": "string",
"pattern": "^[a-z][a-zA-Z0-9-]*$",
"description": "Screen action name (`data-action`). Declared in the `actions` of the screen with a handler and a risk class; the engine adds undo or confirm accordingly."
},
"actionId": {
"type": "string",
"pattern": "^[A-Za-z0-9_-]+$",
"description": "Id of the thing the action applies to (`data-id`), when the button is not inside an element that has one."
},
"target": {
"type": "string",
"description": "Id of the element a behaviour acts on (`data-target`), e.g. the dialog to open."
},
"popoverTarget": {
"type": "string",
"description": "Id of a popover menu this button toggles (`popovertarget`)."
}
},
"description": "One button (Button data)."
}
}
}
}Root: article | a.card — usable without render() by writing the markup directly.
| Attribute | Values | Default | Description |
|---|---|---|---|
data-variant | accent | subtle | (none) | Accent-subtle hero tint or subtle second surface. |
data-interactive | — | — | Whole card is a link (<a href>): on hover second surface + strong border, no shadow. |
aria-labelledby | "{id}-title" | — | Names the article after its title (set when id and title are given). |
| Part | Description |
|---|---|
.card__header | Flex row: title at the start, badge (or a small action) at the end. |
.card__title | The heading (h2–h4). |
.card__body | Content column: the meta line (p.u-muted) and body paragraphs. |
.card__footer | <footer> with the actions at the end side (primary last), above a --color-border divider. |
| State | Description |
|---|---|
[data-interactive]:hover | Second surface + strong border (inside @media (hover: hover)). |
:focus-visible | Global focus ring on the link card. |
<article>; with id + title they are labelled by their heading, so they appear as named regions.<a>; its accessible name is all its text, so keep the content short and lead with the title.render throws when href and actions are combined.accent cards all text uses --color-accent-subtle-text, which is verified on --color-accent-subtle; don't add status colours there, they aren't verified on the tint.accent card per screen.Related: Stat tile · List · Key-value list · Status badge · Button