# States

> Empty, loading, error, offline, undo, optimistic update, conflict, no one responsible, Albert’s suggestion, disabled – each with layout, register, example text from the Sonntag family and its component.

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

A screen has more than one state. Without its states it is not finished. This chapter describes each one: how it looks, which register it speaks in and which component carries it.

| State | Component | Register |
|---|---|---|
| empty | `LeererZustand` | family manager, with Albert as a character |
| loading | `Skelett` | – |
| error | `LeererZustand` without the character | family manager or clerk |
| offline | notice line (proposal) | family manager |
| undo | `Toast` | family manager |
| optimistic update | card immediately, `Toast` after | family manager |
| conflict | `KonfliktKarte`, `BuendelKarte` | family manager |
| no one responsible | card with open outline | family manager |
| Albert’s suggestion | `VorschlagKarte`, `Karte art="vorschlag"` | family manager, clerk in the scan |
| disabled | dedicated roles instead of opacity | – |

## Empty

An empty state says three things: what belongs here, why it is empty, what you can do. Plus exactly one button. Albert may appear here as a character, with a handwritten margin note. Never in lists, in the safe or in finances.

### LeererZustand

Source: `packages/design-system/components/zustaende/LeererZustand.jsx`

Der leere Zustand – drei Dinge und ein Knopf.

```jsx
<LeererZustand mitAlbert
  was="Hier landet die Post der Schule."
  warum="Diese Woche kam nichts an. Sobald etwas kommt, macht Albert daraus einen Vorschlag."
  schritt="Post verbinden"
  randnotiz="Ich schaue täglich rein." />
```

Kein "Fehler", kein "Ups", kein Ausrufezeichen. Albert erscheint hier als Figur – in Listen, im Safe, bei Finanzen nie.

```ts
/**
 * Der leere Zustand: was hierher gehoert, warum es leer ist, ein Knopf.
 */
export interface LeererZustandProps {
  /** Was hierher gehoert - ein Satz, kein Titel: "Hier landet die Post der Schule." */
  was: React.ReactNode;
  /** Warum es leer ist: "Diese Woche kam nichts an." */
  warum?: React.ReactNode;
  /** Was man tun kann, als Verb: "Post verbinden". */
  schritt?: string;
  onSchritt?: () => void;
  /** Albert als Figur - nur in Onboarding, leeren Zustaenden, Briefings, Kinderbereich, Jahresbuch. */
  mitAlbert?: boolean;
  /** Alberts Bleistift-Randnotiz in Reenie Beanie. Nur im Register Erzaehler. */
  randnotiz?: string;
  style?: React.CSSProperties;
}
export function LeererZustand(props: LeererZustandProps): JSX.Element;
```

> **Example**
>
> **What:** Hier landet die Post der Schule. (School mail lands here.) **Why:** Diese Woche kam nichts an. Sobald etwas kommt, macht Albert daraus einen Vorschlag. (Nothing arrived this week. As soon as something comes, Albert turns it into a suggestion.) **Button:** Post verbinden (connect mail).

> **Don't**
>
> “No data available.” · “Oops, nothing here yet!” · an empty screen with a grey icon and nothing else.

## Loading

Never a spinner, never a progress bar as decoration. The skeleton has the shape of what is about to arrive: seven columns for the week, three cards for Today. It breathes between `--karte` and `--knopf-zweit` over `--dauer-atem`. With reduced motion it stands still.

### Skelett

Source: `packages/design-system/components/zustaende/Skelett.jsx`

Das Skelett – die Form dessen, was gleich kommt.

```jsx
<Skelett form="board" spalten={7} zeilen={3} />
```

Nie ein Kreisel, nie ein Fortschrittsbalken. Es atmet zwischen Karte und Zweitknopf-Beige und steht still, wenn das System weniger Bewegung will.

```ts
/** Skelett in der Form des Inhalts. Atmet 300 ms, steht bei reduzierter Bewegung, nie ein Kreisel. */
export interface SkelettProps {
  /** "karte" (eine Karte) | "zeile" (Liste) | "board" (Stundenraster). */
  form?: "karte" | "zeile" | "board";
  /** Zeilen je Spalte. */
  zeilen?: number;
  /** Spalten im Board. */
  spalten?: number;
  style?: React.CSSProperties;
}
export function Skelett(props: SkelettProps): JSX.Element;
```

The skeleton only appears when loading takes longer than a brief moment. Proposal: from 300 ms, so it does not flicker on fast responses.

## Error

An error is an empty state with a different why. No “error”, no “oops”, no red, no exclamation mark. It says what did not work and offers a step. Albert does not appear as a character in errors.

> **Example**
>
> **What:** Die Woche ist gerade nicht geladen. (The week has not loaded right now.) **Why:** Die Verbindung ist abgebrochen, eure Daten sind sicher. (The connection dropped, your data is safe.) **Button:** Noch einmal laden (load again).

Errors in safe, vault and finances speak in the clerk register (*Sachbearbeiter*): sober, complete, stating what is saved and what is not.

## Offline

> **Open**
>
> There is no component and no screen for offline. Proposal: a line below the greeting, “Ohne Netz · Änderungen gehen raus, sobald es wieder da ist” (no connection · changes go out as soon as it is back), in `--text-zweit`. Reading continues. Whatever is loaded stays. Changes show immediately and carry a dashed outline until they are synced.

## Undo

Every change reports with a toast and “Rückgängig” (Undo). Albert states facts, he does not praise. Without a way back there is no toast. Anything that cannot be undone is asked first.

### Toast

Source: `packages/design-system/components/zustaende/Toast.jsx`

Der Toast – eine Feststellung und ein Rückweg.

```jsx
<Toast text="Tobias fährt am Samstag." onZurueck={zurueck} />
```

Albert stellt fest ("Fertig."), er lobt nicht. Ohne `onZurueck` gibt es keinen Toast – was nicht rücknehmbar ist, wird vorher gefragt.

```ts
/** Kurze Rueckmeldung mit Rueckweg. Jedes optimistische Update bekommt einen. */
export interface ToastProps {
  /** Feststellung im Praesens, ohne Lob: "Tobias fährt am Samstag." */
  text: React.ReactNode;
  zurueck?: string;
  onZurueck?: () => void;
  style?: React.CSSProperties;
}
export function Toast(props: ToastProps): JSX.Element;
```

> **Example**
>
> “Tobias fährt am Samstag.” (Tobias drives on Saturday.) · “Oma Grete hat die Fahrt übernommen.” (Oma Grete has taken the drive.) · “Fertig.” (Done.)

The toast sits above the bar on the iPhone and bottom centre on the Mac. It stays until you carry on. Proposal: at least five seconds, so that Oma Grete can read it too.

## Optimistic update

Whoever changes something sees the result immediately. The app does not wait for the server. If it fails, the previous state returns and a toast says what happened. When another person changes something, it says who: “Anna hat die Fahrt übernommen.”

> **Do**
>
> Anna taps “Ich fahre” (I’ll drive). The card shows “Anna fährt” at once, and the toast says “Anna fährt am Samstag.” with “Rückgängig”.

> **Don't**
>
> Anna taps “Ich fahre”, a spinner turns, and only after two seconds does the card change.

## Conflict

Two things collide. The card shows it and holds nobody up. No red, no error. The card states it and names a step.

### KonfliktKarte

Source: `packages/design-system/components/karten/KonfliktKarte.jsx`

Die Karte für zwei Sachen, die sich treffen – sie zeigt es und hält niemanden auf.

```jsx
<KonfliktKarte titel="Lena schwimmt, Felix spielt Handball – beides um 16:00."
  erklaerung="Ein Auto, zwei Orte. Sofia ist bis 17:00 da." wer={["lena", "felix"]}
  schritt="Sofia fragen" zweitSchritt="Später" />
```

Register Family Manager. Kein Rot, kein Ausrufezeichen, kein "Fehler" – die Karte stellt fest und nennt einen Schritt.

```ts
/** Zwei Sachen treffen sich. Sichtbar, nie blockierend, mit naechstem Schritt. */
export interface KonfliktKarteProps {
  /** Was sich trifft, als Feststellung: "Lena schwimmt und Felix spielt - beides 16:00." */
  titel: string;
  /** Warum es klemmt, ein Satz. */
  erklaerung?: React.ReactNode;
  wer?: string[];
  /** Der naechste Schritt als Verb: "Tobias fragen". */
  schritt?: string;
  onSchritt?: () => void;
  zweitSchritt?: string;
  onZweitSchritt?: () => void;
  style?: React.CSSProperties;
}
export function KonfliktKarte(props: KonfliktKarteProps): JSX.Element;
```

When three things run at the same time, one card bundles them into three rows instead of stacking three cards.

### BuendelKarte

Source: `packages/design-system/components/karten/BuendelKarte.jsx`

Die Antwort auf Gleichzeitiges: eine Karte, drei Zeilen – statt drei Karten, die sich stapeln.

```jsx
<BuendelKarte titel="Drei Sachen um 16:00" zeit="16:00 – 17:30" teile={[
  { titel: "Kieferorthopädin", symbol: "arzt", zeit: "16:00 – 17:00", leute: ["anna", "lena"] },
  { titel: "Training", art: "termin", symbol: "sport", zeit: "16:00 – 17:30", leute: ["felix"] },
  { titel: "Abholen Halle Süd", art: "fahrt", zeit: "19:00", offen: true }
]} />
```

Zeilen behalten ihre Bedeutungsfarbe. Eine Zeile ohne Zuständigen wird laut gezeigt: gestrichelter Offen-Rand, nie stillschweigend leer.

```ts
/** Eine Karte fuer Gleichzeitiges: mehrere Sachen zur selben Stunde, jede als Zeile. */
export interface BuendelTeil {
  titel: string;
  art?: "termin" | "fahrt" | "familie";
  /** Sach-Symbol, wenn es nicht aus der Art folgt. */
  symbol?: string;
  zeit?: string;
  leute?: string[];
  /** Ohne Zustaendigen - die Zeile bekommt den gestrichelten Offen-Rand. */
  offen?: boolean;
}
export interface BuendelKarteProps {
  /** Was die Stunde traegt: "Drei Sachen um 16:00". */
  titel: string;
  zeit?: string;
  teile: BuendelTeil[];
  style?: React.CSSProperties;
}
export function BuendelKarte(props: BuendelKarteProps): JSX.Element;
```

## No one responsible

Everything has exactly one person responsible. Something without one is not a neutral state but a problem, and it is shown loudly: a dashed outline in open-pink, `--rand-offen`, and the line “Noch niemand” (no one yet). Never as a fill, never silently empty. The only exception is the task inbox.

> **Do**
>
> “Rückfahrt Auswärtsspiel · Kein Fahrer” (ride home from the away game · no driver) with “Ich fahre”. A pink circle on the Saturday day circle. A dashed dot in the week at a glance.

## Albert’s suggestion

Albert suggests and never acts alone. A suggestion is one observation, one question, two buttons. Yellow with shapes on the iPhone, dashed grey without fill on the Mac. Always with “Vorschlag von Albert” (suggestion from Albert) below.

### VorschlagKarte

Source: `packages/design-system/components/karten/VorschlagKarte.jsx`

Alberts Vorschlag auf dem iPhone – gelb, mit Formen, immer mit zwei Knöpfen.

```jsx
<VorschlagKarte titel="Samstag fehlt ein Fahrer."
  text="Rückfahrt vom Auswärtsspiel, 14:00. Bei Tobias steht ab 13:00 nichts."
  schritt="Tobias fragen" zweitSchritt="Später" />
```

So klingt Albert: eine Beobachtung, eine Frage, zwei Knöpfe. Nie "Ich habe für dich…". Auf dem Mac nimmst du stattdessen `<Karte art="vorschlag">` - dort ist der Vorschlag gestrichelt und ohne Füllung.

```ts
/**
 * Alberts Vorschlag am iPhone: gelbe Karte mit Formen, zwei Knoepfe, Unterzeile.
 */
export interface VorschlagKarteProps {
  /** Eine Beobachtung: "Samstag fehlt ein Fahrer." */
  titel: string;
  /** Zwei kurze Saetze: was er sieht, was daraus folgt. */
  text?: React.ReactNode;
  /** Erster Knopf, ein Verb: "Tobias fragen", "Übernehmen". */
  schritt?: string;
  onSchritt?: () => void;
  /** Zweiter Knopf. Es gibt immer zwei - Albert erledigt nie still. */
  zweitSchritt?: string;
  onZweitSchritt?: () => void;
  unterzeile?: string;
  style?: React.CSSProperties;
}
export function VorschlagKarte(props: VorschlagKarteProps): JSX.Element;
```

> **Example**
>
> “Samstag fehlt ein Fahrer. Rückfahrt vom Auswärtsspiel, 14:00. Bei Tobias steht ab 13:00 nichts.” (Saturday has no driver. Ride home from the away game, 14:00. Tobias is free from 13:00.) Buttons: “Tobias fragen” (ask Tobias), “Später” (later).

> **Don't**
>
> “I’ve entered Tobias as the driver.” Albert enters nothing silently.

In the inbox, the suggestion speaks as the clerk: sender, amount, deadline, filing place, plus “Übernehmen” (accept) and “Rückgängig”.

## Disabled

Disabled is a dedicated, computed role, not opacity: `--text-inaktiv`, `--linie-inaktiv`, `--knopf-inaktiv-flaeche`, `--knopf-inaktiv-text`. A disabled button stays readable and says why it does not work.

| Token | CSS | Value | Dark | Meaning |
| --- | --- | --- | --- | --- |
| knopf-inaktiv-flaeche | var(--knopf-inaktiv-flaeche) | #E9E2D4 | #2B2620 |  |
| knopf-inaktiv-text | var(--knopf-inaktiv-text) | #A29B8F (→ text-inaktiv) | #7A7469 |  |
| text-inaktiv | var(--text-inaktiv) | #A29B8F | #7A7469 | gerechnet, keine Deckkraft |
| linie-inaktiv | var(--linie-inaktiv) | #E4DDCE | #2B2620 | gerechnet, keine Deckkraft |

> **Do**
>
> A button that does not work says why next to it: “Einlösen bestätigen Mama oder Papa.” (Mum or Dad confirm redemption.)

> **Don't**
>
> A button at half opacity with no explanation. Or a greyed-out menu with twelve items of which two work – then the role gets its own entry point.
