# Colour

> Warm paper, ink, a pink accent and one sticky note per person. Every colour is a role with a fixed meaning, light and dark from one source.

Source: https://brand.heyalbert.family/en/farbe/ · Hey Albert brand and design system

Hey Albert looks like a notebook (*Notizbuch*). The background is warm paper, the type is ink. Every person has their own sticky note. A marker highlights what matters now. The sidebar is black, the accent is pink.

Colour comes from the cards, not from the background. The background stays calm: no gradients, no textures, no patterns.

## Roles, never ladders

Every colour has a name that says what it is for. We say “card” (`--karte`), not “Beige 200”. A component names only the role, never the value. That is how dark mode switches without any component knowing about it.

The values live in exactly one place: `packages/design-system/tokens/quelle/`. CSS, Tailwind and Swift are generated from there. Everything on this page is read from there at build time.

```css
/* right: the role */
.termin { background: var(--flaeche-karte); color: var(--text-koerper); border-radius: var(--radius-karte); }

/* wrong: the value. In dark mode the card stays light. */
.termin { background: #EEE8DD; }
```

```swift
// right: the role from AlbertFarbe, it carries light and dark
Text("Schwimmen mit Lena")
    .foregroundStyle(AlbertFarbe.textKoerper)
    .padding(AlbertRaum.karteInnen)
    .background(AlbertFarbe.flaecheKarte, in: .rect(cornerRadius: AlbertForm.radiusKarte))

// wrong: an ad-hoc colour or a switch on the colour scheme
// Color(hex: 0xEEE8DD) · if colorScheme == .dark { … }
```

> **Note**
>
> `swift/Tokens.swift` is generated from the same source. It is not yet included in Xcode. Until then the app carries its own colour catalogue in `AlbertUI`, generated from the CSS tokens.

## Base colours

The base colours are the material. Most components do not use them directly but through the semantic roles further down.

| Token | CSS | Value | Dark | Meaning |
| --- | --- | --- | --- | --- |
| grund | var(--grund) | #F8F2E7 | #14110D | Seitenhintergrund, warmes Creme |
| karte | var(--karte) | #EEE8DD | #2A2620 | Standardkarte (Termin), beige |
| hell | var(--hell) | #FBF7EE | #201C17 | Tagesreiter, Menüs, Badges auf Karten, Suchfeld |
| knopf-zweit | var(--knopf-zweit) | #EDE6D8 | #322D26 | Segment-Regler inaktiv, sekundäre Knöpfe |
| schwarz | var(--schwarz) | #171717 | #0B0A08 | Leiste, primäre Knöpfe, heutiger Tag, aktive Auswahl |
| schwarz-zeiger | var(--schwarz-zeiger) | #2C2C2C | #2C2C2C | Schwarz unter dem Zeiger (Knopf, Kreis, heutiger Reiter) |
| schwarz-druck | var(--schwarz-druck) | #3A3A3A | #3A3A3A | Schwarz beim Drücken |
| tinte | var(--tinte) | #1A1A1A | #F4EFE6 | Fließtext, Titel |
| grau | var(--grau) | #6E6A63 | #A9A297 | Zweittext, Zeiten, Beschriftungen |
| linie | var(--linie) | #D8D0BF | #3D372E | Haarlinien 1 px, Ränder von Feldern und Kreisen |
| rosa | var(--rosa) | #F3B8DF | #F3B8DF | Akzent: Plus, Jetzt-Zeit, aktiver Ort, Zähler, Lupe |
| rosa-hell | var(--rosa-hell) | #F7CDE8 | #F7CDE8 | Zustands-Pillen, Tageskreis mit Offenem |
| gelb | var(--gelb) | #F5D95C | #F5D95C | Familie und Rituale, Alberts Vorschlag (iPhone), Zahlen |
| gelb-dunkel | var(--gelb-dunkel) | #E4C63A | #E4C63A | Formen auf gelben Karten |
| ampel-gruen | var(--ampel-gruen) | #BFD9A6 | #BFD9A6 | Bewertung: gut (HQ) |
| ampel-gelb | var(--ampel-gelb) | #F5D95C (→ gelb) | #F5D95C | Bewertung: Potenzial (HQ) |
| ampel-rot | var(--ampel-rot) | #EBA7A0 | #EBA7A0 | Bewertung: schwach (HQ) |
| text-auf-ampel | var(--text-auf-ampel) | #2B2A25 | #2B2A25 |  |
| blau | var(--blau) | #BFCDF0 | #BFCDF0 | Fahrten |
| blau-dunkel | var(--blau-dunkel) | #9DB1E3 | #9DB1E3 | Formen auf blauen Karten |
| offen | var(--offen) | #B8467F | #F09CC6 | nur gestrichelter Rand für „ohne Zuständigen“ |
| auf-gelb | var(--auf-gelb) | #4A4530 | #4A4530 | Zweittext auf gelben Flächen |
| leistentext | var(--leistentext) | #F4EFE6 | #F4EFE6 | Text und Zeichen auf Schwarz |
| sonntag | var(--sonntag) | #C9A0BD | #B98CAD | gedämpftes Rosa-Grau für den Sonntag |

- `--grund` is the paper. Every surface lies on it.
- `--karte` is the recessed surface, the standard card for appointments.
- `--hell` is what floats: day tabs, menus, the search field, badges on cards.
- `--schwarz` carries the sidebar, the primary button and today’s date.
- `--tinte` is body text and titles. `--grau` is secondary text: times, places, labels.
- `--linie` draws the hairlines.

## Meanings that are never swapped

Four colours carry a meaning. It holds everywhere: in the app, on the website, on social media, in the yearbook.

| Token | CSS | Value | Dark | Meaning |
| --- | --- | --- | --- | --- |
| akzent | var(--akzent) | #F3B8DF (→ rosa) | #F3B8DF | Akzent und „jetzt“ – nie Fehler |
| akzent-hell | var(--akzent-hell) | #F7CDE8 (→ rosa-hell) | #F7CDE8 |  |
| akzent-tief | var(--akzent-tief) | #B8467F (→ offen) | #F09CC6 |  |
| bedeutung-fahrt | var(--bedeutung-fahrt) | #BFCDF0 (→ blau) | #BFCDF0 |  |
| bedeutung-fahrt-tief | var(--bedeutung-fahrt-tief) | #9DB1E3 (→ blau-dunkel) | #9DB1E3 |  |
| bedeutung-familie | var(--bedeutung-familie) | #F5D95C (→ gelb) | #F5D95C |  |
| bedeutung-familie-tief | var(--bedeutung-familie-tief) | #E4C63A (→ gelb-dunkel) | #E4C63A |  |
| bedeutung-termin | var(--bedeutung-termin) | #EEE8DD (→ karte) | #2A2620 |  |

| Colour | Means | Examples |
|---|---|---|
| Pink (`--akzent`, `--rosa`) | accent and now | plus, the current time, the active place, counters, the magnifier |
| Light pink (`--rosa-hell`) | a state that needs attention | the pill “Kein Fahrer” (no driver), a day circle with open items |
| Yellow (`--bedeutung-familie`) | family and ritual | the Sunday round (*Sonntagsrunde*), lunch at Oma Grete’s, large numbers, Albert’s suggestion on iPhone |
| Blue (`--bedeutung-fahrt`) | drive | pick-up, ride home, drive pills |
| Open-pink (`--offen`) | no one responsible | only as a dashed outline, never as a fill |
| Dashed grey (`--rand-vorschlag`) | suggestion from Albert | dashed outline without its own fill |

Pink never means “error”. An error has no alarm colour with us. It says what happened and what you can do.

Yellow is never a text colour. On the paper it is barely legible. Yellow is always a surface; text on it uses `--text-auf-gelb`.

> **Do**
>
> Friday’s drive is blue. If nobody is down as driver yet, the card also gets the dashed outline in `--rand-offen` and the pill “Kein Fahrer” in `--rosa-hell`.

> **Don't**
>
> Pink for an error, red for “urgent”, yellow as a warning, blue for links. Open-pink as a filled surface. A new colour for a new kind of card.

## Semantic roles

Components use these roles. Each points to a base colour and may point to a different one in dark mode.

### Surfaces

| Token | CSS | Value | Dark | Meaning |
| --- | --- | --- | --- | --- |
| flaeche-seite | var(--flaeche-seite) | #F8F2E7 (→ grund) | #14110D |  |
| flaeche-erhoben | var(--flaeche-erhoben) | #FBF7EE (→ hell) | #201C17 | was schwebt: Menü, Reiter, Feld |
| flaeche-karte | var(--flaeche-karte) | #EEE8DD (→ karte) | #2A2620 | Termin |
| flaeche-karte-fahrt | var(--flaeche-karte-fahrt) | #BFCDF0 (→ blau) | #BFCDF0 | Fahrt |
| flaeche-karte-familie | var(--flaeche-karte-familie) | #F5D95C (→ gelb) | #F5D95C | Familie und Ritual |
| flaeche-karte-vorschlag | var(--flaeche-karte-vorschlag) | transparent | transparent | Vorschlag: gestrichelt, ohne Füllung |
| flaeche-leiste | var(--flaeche-leiste) | #171717 (→ schwarz) | #0B0A08 |  |
| flaeche-schleier | var(--flaeche-schleier) | rgba(26, 26, 26, 0.32) | rgba(0, 0, 0, 0.56) | dämpft, was dahinter liegt |
| leiste-ort-zeiger | var(--leiste-ort-zeiger) | rgba(244, 239, 230, 0.08) | rgba(244, 239, 230, 0.08) | Ort in der Leiste unter dem Zeiger |
| leiste-ort-aktiv-zeiger | var(--leiste-ort-aktiv-zeiger) | rgba(244, 239, 230, 0.14) | rgba(244, 239, 230, 0.14) | aktiver Ort in der Leiste unter dem Zeiger |

`--flaeche-schleier` is the only translucent colour. The veil dims whatever lies behind a dialog, a menu or the scanner. There is no frosted glass and no blur.

### Text

| Token | CSS | Value | Dark | Meaning |
| --- | --- | --- | --- | --- |
| text-koerper | var(--text-koerper) | #1A1A1A (→ tinte) | #F4EFE6 |  |
| text-titel | var(--text-titel) | #1A1A1A (→ tinte) | #F4EFE6 |  |
| text-zweit | var(--text-zweit) | #6E6A63 (→ grau) | #A9A297 |  |
| text-auf-leiste | var(--text-auf-leiste) | #F4EFE6 (→ leistentext) | #F4EFE6 |  |
| text-auf-leiste-zweit | var(--text-auf-leiste-zweit) | #9A948B | #8C857A |  |
| text-auf-gelb | var(--text-auf-gelb) | #4A4530 (→ auf-gelb) | #4A4530 |  |
| text-auf-blau | var(--text-auf-blau) | #1A1A1A (→ tinte) | #1A1A1A |  |
| text-auf-blau-zweit | var(--text-auf-blau-zweit) | #4A4F63 | #4A4F63 | Zweittext auf blauen Flächen (Fahrt) |
| text-auf-rosa | var(--text-auf-rosa) | #1A1A1A (→ tinte) | #1A1A1A |  |
| text-inaktiv | var(--text-inaktiv) | #A29B8F | #7A7469 | gerechnet, keine Deckkraft |

Text on yellow, blue and pink has its own roles: `--text-auf-gelb`, `--text-auf-blau`, `--text-auf-rosa`. They stay dark in dark mode because the surface stays light. Use `--tinte` there and you write cream on light at night.

`--text-inaktiv` is a computed colour. Disabled never means “half opacity” here.

### Lines and outlines

| Token | CSS | Value | Dark | Meaning |
| --- | --- | --- | --- | --- |
| linie-haar | var(--linie-haar) | #D8D0BF (→ linie) | #3D372E |  |
| linie-stark | var(--linie-stark) | #C2B8A2 | #524A3D | Kontur 1,5 px statt 1 px |
| linie-inaktiv | var(--linie-inaktiv) | #E4DDCE | #2B2620 | gerechnet, keine Deckkraft |
| linie-jetzt | var(--linie-jetzt) | #1A1A1A (→ tinte) | #F4EFE6 | gestrichelte Jetzt-Achse |
| rand-vorschlag | var(--rand-vorschlag) | #6E6A63 (→ grau) | #A9A297 | gestrichelt grau: Vorschlag von Albert |
| rand-offen | var(--rand-offen) | #B8467F (→ offen) | #F09CC6 | gestrichelt: ohne Zuständigen |
| rand-fokus | var(--rand-fokus) | #B8467F (→ offen) | #F09CC6 | :focus-visible Ring |

### Buttons

| Token | CSS | Value | Dark | Meaning |
| --- | --- | --- | --- | --- |
| knopf-primaer-flaeche | var(--knopf-primaer-flaeche) | #171717 (→ schwarz) | #F4EFE6 |  |
| knopf-primaer-flaeche-zeiger | var(--knopf-primaer-flaeche-zeiger) | #2C2C2C (→ schwarz-zeiger) | #E2DACD | Druck macht die Fläche nicht dunkler, sondern die Schrift ruhiger |
| knopf-primaer-flaeche-druck | var(--knopf-primaer-flaeche-druck) | #3A3A3A (→ schwarz-druck) | #CFC6B7 |  |
| knopf-primaer-text | var(--knopf-primaer-text) | #F4EFE6 (→ leistentext) | #171717 |  |
| knopf-zweit-flaeche | var(--knopf-zweit-flaeche) | #EDE6D8 (→ knopf-zweit) | #322D26 |  |
| knopf-zweit-text | var(--knopf-zweit-text) | #1A1A1A (→ tinte) | #F4EFE6 |  |
| knopf-inaktiv-flaeche | var(--knopf-inaktiv-flaeche) | #E9E2D4 | #2B2620 |  |
| knopf-inaktiv-text | var(--knopf-inaktiv-text) | #A29B8F (→ text-inaktiv) | #7A7469 |  |

The primary button gets lighter on hover and press, not darker. On paper, lightening feels calmer. In dark mode it flips: light surface, dark text.

## Person colours

Every person in the household has a slot and with it a colour. There are eight slots. The Sonntag family fills five: Anna, Tobias, Felix, Lena and Sofia. The grandparents are guests.

| Slot | Surface | Line | Sonntag family |
| --- | --- | --- | --- |
| 1 | var(--person-1) #D5F5C2 | var(--person-linie-1) #8FCF6E | Anna |
| 2 | var(--person-2) #A8E5E5 | var(--person-linie-2) #62BFBF | Tobias |
| 3 | var(--person-3) #F6D0FF | var(--person-linie-3) #D28FE8 | Felix |
| 4 | var(--person-4) #FFD6A5 | var(--person-linie-4) #E0A55F | Lena |
| 5 | var(--person-5) #AEBAFF | var(--person-linie-5) #7B8BE0 | Sofia |
| 6 | var(--person-6) #C7EFD0 | var(--person-linie-6) #7FC993 | Oma Grete |
| 7 | var(--person-7) #FFC9C9 | var(--person-linie-7) #E08F8F | Opa Werner |
| 8 | var(--person-8) #E3DCA8 | var(--person-linie-8) #B7AE6A | Oma Hilde |

Every slot has two faces:

- **The note** (`--person-1` to `--person-8`) is a light surface: avatar, sticky note, personal lane. Text on it is always `--person-text`, which is ink. The note stays light in dark mode too, because a sticky note is a light scrap of paper at night as well.
- **The line** (`--person-linie-1` to `--person-linie-8`) is the stronger step for dots, outlines and lanes. In dark mode it becomes stronger still, otherwise it would disappear.

Three rules always apply:

1. **Colour is never the only carrier.** Every person colour comes with an initial or a name. On the paper the person lines only reach 1.7:1 to 2.9:1. The system promises no contrast there, so initial and name carry the meaning.
2. **One person, one colour, everywhere.** Lena is peach on iPhone, on the Mac and in the campaign. Anyone who knows Anna recognises her colour.
3. **Guests are outlined, household and helpers are filled.** Oma Grete appears as a ring, Sofia as a filled note.

Children design their own space, within contrast guardrails (principle 4).

> **Open**
>
> What a child may choose in their own space is not yet described as a component. Only the limit is fixed: everything stays legible under the contrast rules below, and their own person colour stays theirs.

## Dark mode

Dark mode is not a second design. It redefines the same roles under `:root[data-modus="dunkel"]`. In SwiftUI every role carries both values and decides at runtime.

- `--grund` becomes a deep black-brown, `--karte` a dark beige-grey.
- `--tinte` becomes cream.
- Pink, yellow and blue stay light surfaces. Text on them stays dark.
- The person notes stay light, their lines get stronger.
- `--offen` gets a stronger step so the dashed outline still reads at night.

The swatches on this page switch along. The button at the top right changes the mode.

> **Don't**
>
> Choosing a colour in a component by mode: `if colorScheme == .dark`, `@media (prefers-color-scheme: dark)` inside a component, a second palette for night.

## Contrast

Body text and controls need at least 4.5:1. Surfaces and outlines need at least 3:1. The table computes both modes from the tokens on every build.

| Foreground | Background | Light | Verdict | Dark | Verdict |
| --- | --- | --- | --- | --- | --- |
| tinte | grund | 15.62:1 | AAA | 16.43:1 | AAA |
| tinte | karte | 14.27:1 | AAA | 13.13:1 | AAA |
| grau | grund | 4.83:1 | AA | 7.44:1 | AAA |
| grau | karte | 4.41:1 | 3:1 | 5.94:1 | AA |
| text-inaktiv | grund | 2.47:1 | zu schwach | 4.06:1 | 3:1 |
| tinte | rosa | 10.55:1 | AAA | 1.44:1 | zu schwach |
| tinte | gelb | 12.40:1 | AAA | 1.23:1 | zu schwach |
| tinte | blau | 10.94:1 | AAA | 1.39:1 | zu schwach |
| tinte | person-1 | 14.64:1 | AAA | 1.04:1 | zu schwach |
| tinte | person-5 | 9.34:1 | AAA | 1.63:1 | zu schwach |
| hell | schwarz | 16.77:1 | AAA | 1.17:1 | zu schwach |
| offen | grund | 4.47:1 | 3:1 | 9.21:1 | AAA |
| linie | grund | 1.38:1 | zu schwach | 1.60:1 | zu schwach |

For information, not as a promise: pink, blue and yellow as surfaces on the paper sit at 1.3:1 to 1.5:1. These surfaces separate through their hairline and their content, not through colour distance.

> **Open: grey and disabled are not corrected in the source**
>
> `docs/entscheidungen.md` (04.09.2026) and `docs/22-gestaltung.md` §3 state: `--grau` was moved from `#6E6A63` to `#6C6861` so that secondary text on a card holds 4.5:1. `--text-inaktiv` was moved from `#A29B8F` to `#928B7F` to reach 3:1.
> 
> The token source `tokens/quelle/farben-hell.json`, the generated CSS, `swift/Tokens.swift` and the frozen reference in `tokens/referenz/` still carry the old values. Recomputed:
> 
> - `--grau` on `--karte`: 4.41:1 instead of 4.5:1. With the corrected value it would be 4.54:1. On `--grund` grey holds at 4.83:1.
> - `--text-inaktiv` on `--grund`: 2.47:1, on `--karte` 2.26:1. With the corrected value it would be 3.03:1 on the background, and still only 2.77:1 on a card.
> 
> The correction was apparently lost in the move to the new design system. To decide: update the values in the source, run `pnpm tokens` and renew the reference.

## HQ-only colours

`--ampel-gruen`, `--ampel-gelb`, `--ampel-rot` and `--text-auf-ampel` exist only in the internal HQ. There they rate figures: good, potential, weak. They never appear in the family app. A child never sees a red score.

## In short

> **Do**
>
> - Name the role, never the value.
> - Meaning before taste: a drive is blue, family is yellow, now is pink.
> - Text on coloured surfaces through the `--text-auf-…` roles.
> - An initial or a name next to every person colour.
> - A new colour? Into the source first, light and dark, then compute contrast.

> **Don't**
>
> - Hex values, `rgba()` or Tailwind colours like `bg-pink-200` in a component.
> - Opacity for “disabled”.
> - Gradients, patterns or images as an interface background.
> - Yellow as text, open-pink as a fill, pink as an error.
> - Traffic lights, ranking colours or a red score for children.
