# Ein Theme mit CSS-Variablen bauen: hell, dunkel und Markenfarben

[Canonical HTML page](https://gradiently.design/de/guide/css-custom-properties-theming)

Themes gehen schief, wenn Farben danach benannt werden, wie sie aussehen, statt danach, was sie tun. Benenne die Rollen, verweise sie auf eine Palette, und Hellmodus, Dunkelmodus und eine zweite Marke werden zu je ein paar Zeilen.

## The short version

- Ein Theme mit CSS-Variablen funktioniert am besten in zwei Ebenen: Palettenvariablen mit rohen Farben und semantische Variablen, die beschreiben, wofür jede Farbe da ist.
- Komponenten sollten nur semantische Variablen wie --color-text oder --color-surface lesen, nie direkt Palettenwerte.
- Der Dunkelmodus besteht dann darin, die semantischen Variablen unter einer Media Query oder einem Data-Attribut auf andere Palettenwerte zu verweisen.
- Die Funktion light-dark() und die Eigenschaft color-scheme lassen eine Deklaration in aktuellen Browsern beide Modi halten.
- Ein kleines Inline-Skript im head, das das gespeicherte Theme setzt, bevor die Seite gezeichnet wird, verhindert das Aufblitzen des falschen Themes.

Ein **Theme mit CSS-Variablen** ist eine Gruppe von Custom Properties wie `--color-surface` und `--color-text`, die jede Komponente statt fest kodierter Farben liest. Um das Theme zu wechseln, änderst du die Variablen, meist auf `:root` oder an einem `data-theme`-Attribut, und das ganze Interface folgt. Der Trick, der es skalierbar macht, ist die Aufteilung der Variablen in zwei Ebenen: eine Palette roher Farben und semantische Rollen, die auf diese Palette verweisen.

## Zwei Ebenen: Palette und Rollen

Palettenvariablen sind danach benannt, was sie sind: `--violet-600`, `--ink-900`. Semantische Variablen sind danach benannt, was sie tun: `--color-accent`, `--color-text-muted`. Komponenten lesen nur die zweite Art. Kommt der Dunkelmodus, verweist du die Rollen neu und lässt jede Komponente unberührt. Das ist dieselbe Idee wie bei [Design Tokens](https://gradiently.design/de/guide/design-tokens), direkt in CSS ausgedrückt.

| Ebene | Beispiel | Wer sie liest | Ändert sich, wenn |
| --- | --- | --- | --- |
| Palette | `--violet-600: #7c3aed` | Nur die Rollenebene | Die Marke neu gestaltet wird |
| Rolle | `--color-accent: var(--violet-600)` | Jede Komponente | Theme oder Modus wechseln |
| Komponente | `--button-bg: var(--color-accent)` | Eine Komponente | Eine einzelne Komponente eine lokale Abweichung braucht |

Die Komponentenebene ist optional. Nutze sie nur, wo eine Komponente wirklich von der Rolle abweichen muss.

```css
:root {
  /* Palette: raw values, named for what they are */
  --paper-50: #faf8f5;
  --paper-100: #f1ede6;
  --ink-900: #14121f;
  --ink-700: #3d3a4f;
  --ink-500: #6b6880;
  --violet-600: #6d28d9;
  --violet-300: #c4b5fd;
  --night-950: #0d0b16;
  --night-900: #17142a;
  --night-800: #221e3a;

  /* Roles: what each colour is for (light mode) */
  --color-bg: var(--paper-50);
  --color-surface: var(--paper-100);
  --color-text: var(--ink-900);
  --color-text-muted: var(--ink-500);
  --color-border: color-mix(in oklab, var(--ink-900) 12%, transparent);
  --color-accent: var(--violet-600);
  --color-on-accent: #ffffff;
}

body { background: var(--color-bg); color: var(--color-text); }
.card { background: var(--color-surface); border: 1px solid var(--color-border); }
.button { background: var(--color-accent); color: var(--color-on-accent); }
```

Komponenten erwähnen nie Violett oder Tinte. Genau diese eine Regel macht das Theme austauschbar.

- bg: #faf8f5
- surface: #f1ede6
- text: #14121f
- text-muted: #6b6880
- accent: #6d28d9

Die Rollen des hellen Themes. Ein warmer Papiergrund statt reinem Weiß lässt den violetten Akzent ruhiger wirken.

### Rollennamen, die ein Redesign überleben

Ein Rollenname sollte auch nach einer Farbänderung noch stimmen. `--light-grey` ist im Dunkelmodus nicht mehr hell, und `--blue` lügt an dem Tag, an dem die Marke grün wird. Benenne stattdessen die Aufgabe und das Paar, damit die nächste Person, die das Stylesheet liest, weiß, welche Farbe auf welche gehört.

### Namen, die brechen

- `--blue`, `--purple-button`
- `--light-grey-bg`
- `--white-text`
- `--dark-border`

### Namen, die halten

- `--color-accent`, `--color-on-accent`
- `--color-surface`
- `--color-text`
- `--color-border`

## Dunkelmodus mit CSS-Variablen ergänzen

Der Dunkelmodus verweist die Rollen neu. Respektiere standardmäßig die Einstellung des Betriebssystems und lass ein `data-theme`-Attribut sie überschreiben, wenn der Leser selbst wählt. Setz auch `color-scheme`, damit Scrollbalken, Formularelemente und die Standardfläche passen.

```css
:root { color-scheme: light; }

/* Follow the system unless the reader picked light */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;
    --color-bg: var(--night-950);
    --color-surface: var(--night-900);
    --color-text: #f2f0fa;
    --color-text-muted: #a5a1bd;
    --color-border: color-mix(in oklab, #ffffff 12%, transparent);
    --color-accent: var(--violet-300);
    --color-on-accent: var(--ink-900);
  }
}

/* The reader picked dark explicitly */
:root[data-theme="dark"] {
  color-scheme: dark;
  --color-bg: var(--night-950);
  --color-surface: var(--night-900);
  --color-text: #f2f0fa;
  --color-text-muted: #a5a1bd;
  --color-border: color-mix(in oklab, #ffffff 12%, transparent);
  --color-accent: var(--violet-300);
  --color-on-accent: var(--ink-900);
}
```

Die dunklen Werte stehen zweimal da: einmal für die Systemeinstellung und einmal für eine ausdrückliche Wahl. Ein Präprozessor-Mixin oder `light-dark()` beseitigt die Wiederholung.

- bg: #0d0b16
- surface: #17142a
- text: #f2f0fa
- text-muted: #a5a1bd
- accent: #c4b5fd

Die Rollen des dunklen Themes. Der Akzent hebt sich zu einem helleren Violett, weil das Violett des Hellmodus auf fast schwarzem Grund zu dunkel zum Lesen ist.

Der Dunkelmodus ist kein invertierter Hellmodus. Flächen werden heller, je höher sie liegen, Akzente hellen auf, und Schatten weichen Rahmen. [Dark-Mode-Design](https://gradiently.design/de/guide/dark-mode-design) und [Verläufe im Dark Mode](https://gradiently.design/de/guide/dark-mode-gradients) behandeln die visuelle Seite.

### Die Abkürzung light-dark()

Aktuelle Browser unterstützen `light-dark()`, das einen hellen und einen dunklen Wert nimmt und anhand des `color-scheme` des Elements einen davon wählt. Setz `color-scheme: light dark` auf das Root-Element, um dem System zu folgen, oder erzwinge einen Modus über das Attribut, und jede Rolle wird zu einer einzigen Zeile: `--color-bg: light-dark(var(--paper-50), var(--night-950))`. Behalte die längere Form, wenn du Browser von vor 2024 unterstützen musst.

## Ein Theme-Umschalter ohne Aufblitzen

Wird die gespeicherte Wahl erst nach dem Rendern der Seite angewendet, sehen Leser, die Dunkel gewählt haben, bei jedem Laden ein weißes Aufblitzen. Setz das Attribut in einem winzigen Inline-Skript im `head`, bevor ein Stylesheet den Body zeichnet.

```html
<head>
  <script>
    (function () {
      var saved = localStorage.getItem("theme");
      if (saved === "light" || saved === "dark") {
        document.documentElement.dataset.theme = saved;
      }
    })();
  </script>
  <link rel="stylesheet" href="/styles.css">
</head>
```

Läuft vor dem ersten Zeichnen. Der Umschalter muss dann nur `dataset.theme` setzen und den Wert in `localStorage` speichern.

1. **Biete drei Optionen an** Hell, dunkel und System. Entfernst du das Attribut, übernimmt wieder `prefers-color-scheme`.
2. **Speichere nur ausdrückliche Wahlen** Speichere `light` oder `dark`; lösche den Schlüssel für System, damit eine spätere Änderung im Betriebssystem respektiert wird.
3. **Vermeide Übergänge beim Laden** Wenn Farben animiert wechseln, aktiviere den Übergang erst nach dem ersten Zeichnen, sonst animiert die Seite beim Laden von hell nach dunkel.

## Marken-Themes jenseits von hell und dunkel

Dieselben zwei Ebenen tragen mehrere Marken in einer Codebasis. Jede Marke liefert unter einem `data-brand`-Attribut eigene Paletten- und Rollenwerte, und die Modi funktionieren weiter darüber. Mit `color-mix()` leitest du Hover-Zustände und helle Töne aus einem Akzent pro Marke ab, wie [color-mix() in CSS](https://gradiently.design/de/guide/css-color-mix) zeigt.

```css
[data-brand="harbour"] {
  --color-accent: #0f766e;
  --gradient-hero: linear-gradient(150deg, #042f2e 0%, #0f766e 55%, #5eead4 100%);
}

[data-brand="ember"] {
  --color-accent: #c2410c;
  --gradient-hero: linear-gradient(150deg, #431407 0%, #c2410c 55%, #fdba74 100%);
}

.button:hover { background: color-mix(in oklab, var(--color-accent), black 12%); }
.hero { background: var(--gradient-hero); }
```

Auch Verläufe können Tokens sein. Speichere den ganzen Wert in einer Variable, und der Hero wechselt mit der Marke.

- Harbour-Hero: `linear-gradient(150deg, #042f2e 0%, #0f766e 55%, #5eead4 100%)`
- Ember-Hero: `linear-gradient(150deg, #431407 0%, #c2410c 55%, #fdba74 100%)`

Die zwei Hero-Tokens aus dem Code oben. Gleiche Struktur, gleicher Winkel, andere Marke.

Weil Custom Properties vererbt werden, muss ein Theme nicht die ganze Seite abdecken. Setz `data-theme="dark"` auf einen Abschnitt, etwa ein Werbeband oder einen Footer, und alles darin liest die dunklen Rollen, während der Rest der Seite hell bleibt. Schreib den dunklen Selektor dafür als `[data-theme="dark"]` statt `:root[data-theme="dark"]` und setz `color-scheme` auch am Abschnitt.

> **Verläufe wechseln nicht von selbst weich** Eine geänderte Verlaufsvariable tauscht den Hintergrund sofort aus. Um zwischen zwei Verläufen zu animieren, registriere die Farbstopps mit `@property`, erklärt in [CSS @property](https://gradiently.design/de/guide/css-property-animation).

## Web und Designs auf einer Palette halten

Ein Theme in CSS ist nur die Hälfte einer Marke. Dieselben Farben sollten in deinen Social Posts, Präsentationen und E-Mails auftauchen. Das Markenkit von Gradiently hält deine Paletten, Logos und Schriften für Überschriften und Fließtext, und der Designer wendet es an, wenn er ein Design setzt, sodass der Post vom Montag denselben Akzent nutzt wie deine Website. Wenn du diese Farben noch auswählst, starte mit [Markenfarben wählen](https://gradiently.design/de/guide/how-to-choose-brand-colors).

## FAQ

### Wie baue ich ein Theme mit CSS-Variablen?

Definiere Palettenvariablen mit rohen Farben und dann semantische Variablen wie `--color-bg`, die auf sie verweisen. Komponenten lesen nur die semantischen, und Themes verweisen sie neu.

### Wie ergänze ich einen Dunkelmodus mit CSS-Variablen?

Überschreib die semantischen Variablen in `@media (prefers-color-scheme: dark)` und unter einem Selektor `[data-theme="dark"]` für Leser, die ihn wählen.

### Wie verhindere ich, dass das Theme beim Laden aufblitzt?

Lies das gespeicherte Theme in einem kleinen Inline-Skript im `head` und setz das Attribut, bevor das Stylesheet die Seite zeichnet.

### Was macht light-dark() in CSS?

Es liefert im Hellmodus seinen ersten und im Dunkelmodus seinen zweiten Wert, anhand des `color-scheme` des Elements. Es funktioniert in aktuellen großen Browsern.

### Kann ich einen Verlauf in einer CSS-Variable speichern?

Ja. Speichere den ganzen Verlauf als Wert und nutze ihn in `background`. Um zwischen Verläufen zu animieren, registriere die Farbstopps mit `@property`.
