Streak strip

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

  1. Mi: trainiert
  2. Do: trainiert
  3. Fr: heute
  4. Sa
  5. So
  6. Mo
  7. 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

  1. Mo: trainiert
  2. Di
  3. Mi: trainiert
  4. Do: heute
  5. Fr
  6. Sa
  7. 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)

  1. Mo
  2. Di: trainiert
  3. Mi: heute
  4. Do
  5. Fr
  6. Sa
  7. 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.

FieldTypeRequiredDefaultDescription
daysarray of objectsrequired—Seven days in display order.
min 7 items · max 7 items
days[].daystringrequired—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.
goalintegeroptional—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
weeksintegeroptional—Current streak in weeks (weeks in a row with the goal met), shown after the flame icon: "1 Woche", "2 Wochen".
≥ 0
labelstringoptional—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

AttributeValuesDefaultDescription
aria-label (on .streak__days)text—Summary of the week.

Parts

PartDescription
.streak__summarySummary row: flame icon + streak in weeks; .streak__goal ("1/3", muted, tabular) at the end.
.streak__daysThe <ol> of seven days, with the summary label.
.streak__dot32 px day circle; holds the check icon on done days (decorative).
.streak__dayWeekday caption under the circle (--text-xs).
li > .u-visually-hiddenAnnounced day state ("trainiert", "heute").

States

StateDescription
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

DoCount planned sessions done, not days in a row.
DoOffer a "Pause" for injury or holidays elsewhere on the screen.
Don'tDon't shame ("Du hast 3 Tage nichts gemacht").
Don'tDon't show streaks of minors on public screens or leaderboards.

Related: Progress segments · Progress ring · Action tile