Clubstable.stripes
Stripe chips
The stripes (requirements) a student collects towards the next grade, as labelled chips.
Each stripe is one requirement of the club's grading system (default for TKD Bern: Form, Kyorugi, 5 Tenets, Hosinsul, Theorie). Look (design guide §9.7): --radius-sm chip with a small colour square (outlined with --belt-edge) and the name. Earned: --color-surface with a --color-border-strong border, check icon, --color-text. Not earned: dashed --color-border outline, --color-text-muted, no check. The state is also in the accessible name ("Form: erhalten").
Read-only by default. With editable: true every stripe is a toggle button; the page listens for click on [data-stripe] and saves the change (with the offline outbox in the Trainer app).
Examples
Read-only (profile, applicant card)
- Form: erhalten
- Kyorugi: erhalten
- 5 Tenets: offen
- Hosinsul: offen
- Theorie: offen
Data
render({
"label": "Streifen für 5. Kup",
"stripes": [
{
"key": "form",
"label": "Form",
"color": "red",
"earned": true,
"earnedAt": "2026-09-14"
},
{
"key": "kyorugi",
"label": "Kyorugi",
"color": "blue",
"earned": true
},
{
"key": "tenets",
"label": "5 Tenets",
"color": "green",
"earned": false
},
{
"key": "hosinsul",
"label": "Hosinsul",
"color": "yellow",
"earned": false
},
{
"key": "theory",
"label": "Theorie",
"color": "black",
"earned": false
}
]
})Markup
<ul class="stripes" role="list" aria-label="Streifen für 5. Kup"><li class="stripe" data-stripe="form" style="--stripe-color: var(--stripe-red)" data-earned title="erhalten am 14.09.2026">Form<span class="u-visually-hidden">: erhalten</span><span class="stripe__check"><svg class="icon" data-size="sm" aria-hidden="true"><use href="../../packages/ui/icons/icons.svg#check"/></svg></span></li><li class="stripe" data-stripe="kyorugi" style="--stripe-color: var(--stripe-blue)" data-earned>Kyorugi<span class="u-visually-hidden">: erhalten</span><span class="stripe__check"><svg class="icon" data-size="sm" aria-hidden="true"><use href="../../packages/ui/icons/icons.svg#check"/></svg></span></li><li class="stripe" data-stripe="tenets" style="--stripe-color: var(--stripe-green)">5 Tenets<span class="u-visually-hidden">: offen</span><span class="stripe__check"><svg class="icon" data-size="sm" aria-hidden="true"><use href="../../packages/ui/icons/icons.svg#check"/></svg></span></li><li class="stripe" data-stripe="hosinsul" style="--stripe-color: var(--stripe-yellow)">Hosinsul<span class="u-visually-hidden">: offen</span><span class="stripe__check"><svg class="icon" data-size="sm" aria-hidden="true"><use href="../../packages/ui/icons/icons.svg#check"/></svg></span></li><li class="stripe" data-stripe="theory" style="--stripe-color: var(--stripe-black)">Theorie<span class="u-visually-hidden">: offen</span><span class="stripe__check"><svg class="icon" data-size="sm" aria-hidden="true"><use href="../../packages/ui/icons/icons.svg#check"/></svg></span></li></ul>Editable (Trainer app, student panel)
Data
render({
"label": "Streifen von Lea Müller",
"editable": true,
"stripes": [
{
"key": "form",
"label": "Form",
"color": "red",
"earned": true
},
{
"key": "kyorugi",
"label": "Kyorugi",
"color": "blue",
"earned": false
},
{
"key": "tenets",
"label": "5 Tenets",
"color": "green",
"earned": false
}
]
})Markup
<ul class="stripes" role="list" aria-label="Streifen von Lea Müller"><li><button type="button" class="stripe" data-stripe="form" aria-pressed="true" style="--stripe-color: var(--stripe-red)" data-earned>Form<span class="stripe__check"><svg class="icon" data-size="sm" aria-hidden="true"><use href="../../packages/ui/icons/icons.svg#check"/></svg></span></button></li><li><button type="button" class="stripe" data-stripe="kyorugi" aria-pressed="false" style="--stripe-color: var(--stripe-blue)">Kyorugi<span class="stripe__check"><svg class="icon" data-size="sm" aria-hidden="true"><use href="../../packages/ui/icons/icons.svg#check"/></svg></span></button></li><li><button type="button" class="stripe" data-stripe="tenets" aria-pressed="false" style="--stripe-color: var(--stripe-green)">5 Tenets<span class="stripe__check"><svg class="icon" data-size="sm" aria-hidden="true"><use href="../../packages/ui/icons/icons.svg#check"/></svg></span></button></li></ul>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 | optional | "Streifen" | Accessible name of the group, e.g. "Streifen für 5. Kup". Required when several groups are on one page. |
editable | boolean | optional | false | Render toggle buttons (aria-pressed) instead of a static list. |
stripes | array of objects | required | — | Requirements in the order of the grading system. min 1 items · max 10 items |
stripes[].key | string | required | — | Stable id of the requirement in the grading system, e.g. "form". Written to data-stripe.pattern ^[a-z0-9-]+$ |
stripes[].label | string | required | — | Localised requirement name shown on the chip. min 1 chars |
stripes[].color | "red" | "blue" | "green" | "yellow" | "black" | required | — | Stripe colour; maps to --stripe-{color}. |
stripes[].earned | boolean | required | — | Whether the student has this stripe. |
stripes[].earnedAt | string | optional | — | Date earned (ISO); shown as tooltip "erhalten am …" when set. pattern ^\d{4}-\d{2}-\d{2}$ |
JSON Schema
{
"type": "object",
"additionalProperties": false,
"required": [
"stripes"
],
"properties": {
"label": {
"type": "string",
"description": "Accessible name of the group, e.g. \"Streifen für 5. Kup\". Required when several groups are on one page.",
"default": "Streifen"
},
"editable": {
"type": "boolean",
"default": false,
"description": "Render toggle buttons (aria-pressed) instead of a static list."
},
"stripes": {
"type": "array",
"minItems": 1,
"maxItems": 10,
"description": "Requirements in the order of the grading system.",
"items": {
"type": "object",
"additionalProperties": false,
"required": [
"key",
"label",
"color",
"earned"
],
"properties": {
"key": {
"type": "string",
"pattern": "^[a-z0-9-]+$",
"description": "Stable id of the requirement in the grading system, e.g. \"form\". Written to `data-stripe`."
},
"label": {
"type": "string",
"minLength": 1,
"description": "Localised requirement name shown on the chip."
},
"color": {
"type": "string",
"enum": [
"red",
"blue",
"green",
"yellow",
"black"
],
"description": "Stripe colour; maps to `--stripe-{color}`."
},
"earned": {
"type": "boolean",
"description": "Whether the student has this stripe."
},
"earnedAt": {
"type": "string",
"pattern": "^\\d{4}-\\d{2}-\\d{2}$",
"description": "Date earned (ISO); shown as tooltip \"erhalten am …\" when set."
}
}
}
}
}
}Markup & states
Root: ul.stripes — usable without render() by writing the markup directly.
Attributes
| Attribute | Values | Default | Description |
|---|---|---|---|
aria-label | text | — | Group name (from label). |
role="list" | — | — | Keeps list semantics with list-style removed. |
Parts
| Part | Description |
|---|---|
.stripe | One requirement (li, or button when editable) with data-stripe="{key}". |
.stripe::before | Colour square (--stripe-color, --belt-edge outline), drawn by CSS. |
.stripe__check | 16 px check icon, always rendered, shown only when earned/pressed — so page code only toggles the attributes. |
.u-visually-hidden | Read-only chips: ": erhalten" / ": offen" so the state is in the accessible name. |
States
| State | Description |
|---|---|
[data-earned] | Earned: surface background, solid --color-border-strong border, check icon, full text colour. |
(not earned) | Dashed --color-border outline, muted text, no check. |
[aria-pressed="true|false"] | Editable form: toggle state, mirrors data-earned (pressed is styled as earned). |
CSS custom properties
| Property | Default | Description |
|---|---|---|
--stripe-color | — | Set per chip to var(--stripe-{color}). |
Behaviour & events
Module: (page code)
No built-in behaviour. Listen for clicks on [data-stripe] inside the group, toggle aria-pressed/data-earned, save, and show the save status.
Events
| Event | Detail | Description |
|---|---|---|
click | event.target.closest("[data-stripe]").dataset.stripe → key | Native click on an editable chip. |
Accessibility
- Every chip shows its name — never colour-only dots (audit finding: stripes were unlabeled dots).
- Editable chips are buttons with
aria-pressed, so screen readers announce "Form, Umschalter, gedrückt". - The earned date is a tooltip only; important dates must also be visible elsewhere.
Guidance
Related: Belt badge · Save status