Clubstable.streak
Streak strip
One week as seven day circles — trained days ticked, today ringed — with a summary row (flame + streak in weeks + sessions/goal).
The streak strip motivates athletes by showing this week's planned sessions done (UX guide §13). It counts planned sessions, not calendar days, so rest days don't break anything: a day without training is just an empty circle, never a red failure.
Exactly seven days in display order (usually starting today or on Monday). Each day is done (success circle with a check), today (2 px accent ring) or none (plain --color-surface-2 circle). The list gets a summary label; by default "Diese Woche: {done} von {goal} Trainings" (or "… {done} Trainings" without a goal). Pass label to say more ("…, heute ist Freitag").
Leading summary row (design guide §9.7): flame icon + the streak in weeks ("2 Wochen", from weeks) and, at the end, this week's sessions against the goal ("1/3", from the done days and goal). It is shown when weeks or goal is set. Meaning is carried by icon and text, not colour alone.
Show it to the athlete themself (and their coach/guardians), never as a public comparison between minors.
Examples
Mid-week with streak summary
2 Wochen2/3
- Mi: trainiert
- Do: trainiert
- Fr: heute
- Sa
- So
- Mo
- Di
Data
render({
"goal": 3,
"weeks": 2,
"label": "Diese Woche: 2 von 3 Trainings, heute ist Freitag",
"days": [
{
"day": "Mi",
"state": "done"
},
{
"day": "Do",
"state": "done"
},
{
"day": "Fr",
"state": "today"
},
{
"day": "Sa"
},
{
"day": "So"
},
{
"day": "Mo"
},
{
"day": "Di"
}
]
})Markup
<div class="streak"><p class="streak__summary"><svg class="icon" aria-hidden="true"><use href="../../packages/ui/icons/icons.svg#flame"/></svg><span>2 Wochen</span><span class="streak__goal">2/3</span></p><ol class="streak__days" aria-label="Diese Woche: 2 von 3 Trainings, heute ist Freitag"><li data-state="done"><span class="streak__dot" aria-hidden="true"><svg class="icon" data-size="sm" aria-hidden="true"><use href="../../packages/ui/icons/icons.svg#check"/></svg></span><span class="streak__day">Mi</span><span class="u-visually-hidden">: trainiert</span></li><li data-state="done"><span class="streak__dot" aria-hidden="true"><svg class="icon" data-size="sm" aria-hidden="true"><use href="../../packages/ui/icons/icons.svg#check"/></svg></span><span class="streak__day">Do</span><span class="u-visually-hidden">: trainiert</span></li><li data-state="today"><span class="streak__dot" aria-hidden="true"></span><span class="streak__day">Fr</span><span class="u-visually-hidden">: heute</span></li><li><span class="streak__dot" aria-hidden="true"></span><span class="streak__day">Sa</span></li><li><span class="streak__dot" aria-hidden="true"></span><span class="streak__day">So</span></li><li><span class="streak__dot" aria-hidden="true"></span><span class="streak__day">Mo</span></li><li><span class="streak__dot" aria-hidden="true"></span><span class="streak__day">Di</span></li></ol></div>Week from Monday, goal only
2/4
- Mo: trainiert
- Di
- Mi: trainiert
- Do: heute
- Fr
- Sa
- So
Data
render({
"goal": 4,
"days": [
{
"day": "Mo",
"state": "done"
},
{
"day": "Di"
},
{
"day": "Mi",
"state": "done"
},
{
"day": "Do",
"state": "today"
},
{
"day": "Fr"
},
{
"day": "Sa"
},
{
"day": "So"
}
]
})Markup
<div class="streak"><p class="streak__summary"><svg class="icon" aria-hidden="true"><use href="../../packages/ui/icons/icons.svg#flame"/></svg><span class="streak__goal">2/4</span></p><ol class="streak__days" aria-label="Diese Woche: 2 von 4 Trainings"><li data-state="done"><span class="streak__dot" aria-hidden="true"><svg class="icon" data-size="sm" aria-hidden="true"><use href="../../packages/ui/icons/icons.svg#check"/></svg></span><span class="streak__day">Mo</span><span class="u-visually-hidden">: trainiert</span></li><li><span class="streak__dot" aria-hidden="true"></span><span class="streak__day">Di</span></li><li data-state="done"><span class="streak__dot" aria-hidden="true"><svg class="icon" data-size="sm" aria-hidden="true"><use href="../../packages/ui/icons/icons.svg#check"/></svg></span><span class="streak__day">Mi</span><span class="u-visually-hidden">: trainiert</span></li><li data-state="today"><span class="streak__dot" aria-hidden="true"></span><span class="streak__day">Do</span><span class="u-visually-hidden">: heute</span></li><li><span class="streak__dot" aria-hidden="true"></span><span class="streak__day">Fr</span></li><li><span class="streak__dot" aria-hidden="true"></span><span class="streak__day">Sa</span></li><li><span class="streak__dot" aria-hidden="true"></span><span class="streak__day">So</span></li></ol></div>Days only (no summary row)
- Mo
- Di: trainiert
- Mi: heute
- Do
- Fr
- Sa
- So
Data
render({
"days": [
{
"day": "Mo"
},
{
"day": "Di",
"state": "done"
},
{
"day": "Mi",
"state": "today"
},
{
"day": "Do"
},
{
"day": "Fr"
},
{
"day": "Sa"
},
{
"day": "So"
}
]
})Markup
<div class="streak"><ol class="streak__days" aria-label="Diese Woche: 1 Trainings"><li><span class="streak__dot" aria-hidden="true"></span><span class="streak__day">Mo</span></li><li data-state="done"><span class="streak__dot" aria-hidden="true"><svg class="icon" data-size="sm" aria-hidden="true"><use href="../../packages/ui/icons/icons.svg#check"/></svg></span><span class="streak__day">Di</span><span class="u-visually-hidden">: trainiert</span></li><li data-state="today"><span class="streak__dot" aria-hidden="true"></span><span class="streak__day">Mi</span><span class="u-visually-hidden">: heute</span></li><li><span class="streak__dot" aria-hidden="true"></span><span class="streak__day">Do</span></li><li><span class="streak__dot" aria-hidden="true"></span><span class="streak__day">Fr</span></li><li><span class="streak__dot" aria-hidden="true"></span><span class="streak__day">Sa</span></li><li><span class="streak__dot" aria-hidden="true"></span><span class="streak__day">So</span></li></ol></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 |
|---|---|---|---|---|
days | array of objects | required | — | Seven days in display order. min 7 items · max 7 items |
days[].day | string | required | — | Short weekday shown under the circle ("Mo", "Di"). min 1 chars · max 3 chars |
days[].state | "done" | "today" | "none" | optional | "none" | done: trained (check); today: today, not yet done (ring); none: no session. |
goal | integer | optional | — | Planned sessions this week; used in the default summary label ("2 von 3 Trainings") and the trailing "2/3" of the summary row. ≥ 1 · ≤ 7 |
weeks | integer | optional | — | Current streak in weeks (weeks in a row with the goal met), shown after the flame icon: "1 Woche", "2 Wochen". ≥ 0 |
label | string | optional | — | Full accessible summary of the week, overriding the default. |
JSON Schema
{
"type": "object",
"additionalProperties": false,
"required": [
"days"
],
"properties": {
"days": {
"type": "array",
"minItems": 7,
"maxItems": 7,
"description": "Seven days in display order.",
"items": {
"type": "object",
"additionalProperties": false,
"required": [
"day"
],
"properties": {
"day": {
"type": "string",
"minLength": 1,
"maxLength": 3,
"description": "Short weekday shown under the circle (\"Mo\", \"Di\")."
},
"state": {
"type": "string",
"enum": [
"done",
"today",
"none"
],
"default": "none",
"description": "`done`: trained (check); `today`: today, not yet done (ring); `none`: no session."
}
}
}
},
"goal": {
"type": "integer",
"minimum": 1,
"maximum": 7,
"description": "Planned sessions this week; used in the default summary label (\"2 von 3 Trainings\") and the trailing \"2/3\" of the summary row."
},
"weeks": {
"type": "integer",
"minimum": 0,
"description": "Current streak in weeks (weeks in a row with the goal met), shown after the flame icon: \"1 Woche\", \"2 Wochen\"."
},
"label": {
"type": "string",
"description": "Full accessible summary of the week, overriding the default."
}
}
}Markup & states
Root: div.streak — usable without render() by writing the markup directly.
Attributes
| Attribute | Values | Default | Description |
|---|---|---|---|
aria-label (on .streak__days) | text | — | Summary of the week. |
Parts
| Part | Description |
|---|---|
.streak__summary | Summary row: flame icon + streak in weeks; .streak__goal ("1/3", muted, tabular) at the end. |
.streak__days | The <ol> of seven days, with the summary label. |
.streak__dot | 32 px day circle; holds the check icon on done days (decorative). |
.streak__day | Weekday caption under the circle (--text-xs). |
li > .u-visually-hidden | Announced day state ("trainiert", "heute"). |
States
| State | Description |
|---|---|
li[data-state="done"] | --color-success circle with a white check icon. |
li[data-state="today"] | 2 px --color-accent ring, accent caption. |
Accessibility
- The ordered list carries a summary label, so the week is understood without visiting every day.
- Each day's state is also text (visually hidden), not only the check or ring.
- The summary row is plain text ("2 Wochen", "2/3"); the flame icon is decorative.
- No colour-coded failure states — missed days are neutral.
Guidance
Related: Progress segments · Progress ring · Action tile