Back

Erstellen eines Theme-Switchers mit CSS-Variablen

Erstellen eines Theme-Switchers mit CSS-Variablen

Fest codierte Farbwerte, die über ein Stylesheet verstreut sind, machen das Wechseln von Themes zu einer mühsamen Angelegenheit. Wenn Sie eine Farbe ändern möchten, müssen Sie sich durch Dutzende von Deklarationen wühlen. CSS-Variablen lösen dieses Problem elegant – definieren Sie Ihre Design-Tokens einmal und wechseln Sie das Theme anschließend, indem Sie einfach ein einzelnes Attribut am Root-Element anpassen.

Dieser Artikel zeigt Schritt für Schritt, wie Sie einen wartbaren Theme-Switcher entwickeln: einen, der Systemeinstellungen automatisch berücksichtigt, auf manuelle Benutzerentscheidungen reagiert und diese sitzungsübergreifend speichert.

Wichtigste Erkenntnisse

  • CSS Custom Properties sind dynamisch und werden zur Laufzeit aktualisiert, was sie zu idealen Design-Tokens für Theming macht.
  • Ein data-theme-Attribut am Root-Element bietet eine saubere und skalierbare Möglichkeit, zwischen beliebig vielen Themes zu wechseln.
  • Die Media Query prefers-color-scheme übernimmt die Systemvorgaben, während JavaScript und localStorage manuelle Benutzerentscheidungen speichern.
  • Ein kleines Inline-Skript im <head> verhindert das kurzzeitige Aufblitzen des falschen Themes beim Laden der Seite.
  • Die Verwendung der color-scheme-Eigenschaft stellt sicher, dass die native Browser-UI sich an Ihr aktives Theme anpasst.

Warum CSS-Variablen die richtige Grundlage für Theming sind

CSS Custom Properties sind dynamisch. Im Gegensatz zu Sass-Variablen, die zu statischen Werten kompiliert werden, leben CSS-Variablen im Browser und können zur Laufzeit aktualisiert werden – durch Media Queries, durch JavaScript oder durch ein übergeordnetes Element, das seinen Zustand ändert.

Genau das macht sie zu idealen Design-Tokens. Definieren Sie Ihre Surface-Farben, Textfarben und Akzentfarben als Variablen auf :root und referenzieren Sie diese Variablen dann im gesamten Stylesheet. Das Wechseln von Themes wird zu einer einzelnen DOM-Änderung statt zu einem Stylesheet-Austausch.

Einrichten Ihrer Theme-Tokens

Definieren Sie zunächst Ihr Light-Theme als Standard und deklarieren Sie dann eine Dark-Variante über einen data-theme-Attributselektor:

:root {
  color-scheme: light;
  --color-bg: #ffffff;
  --color-text: #1a1a1a;
  --color-primary: #302ae6;
  --color-surface: #f4f4f4;
}

[data-theme="dark"] {
  color-scheme: dark;
  --color-bg: #161625;
  --color-text: #e1e1ff;
  --color-primary: #9a97f3;
  --color-surface: #1e1e30;
}

Die Eigenschaft color-scheme sollte unbedingt einbezogen werden. Sie teilt dem Browser mit, welcher Modus aktiv ist, sodass native UI-Elemente – Scrollbalken, Formularfelder, Fokusrahmen – sich automatisch an Ihr Theme anpassen.

Nun verwenden Sie diese Tokens in Ihrem gesamten Stylesheet:

body {
  background-color: var(--color-bg);
  color: var(--color-text);
}

Systemeinstellungen mit prefers-color-scheme respektieren

Systemeinstellungen und manuelles Theme-Switching lösen unterschiedliche Probleme. prefers-color-scheme behandelt den automatischen Fall – Nutzer, die ihr Betriebssystem auf den Dark Mode eingestellt haben und erwarten, dass Websites diesem folgen:

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;
    --color-bg: #161625;
    --color-text: #e1e1ff;
    --color-primary: #9a97f3;
    --color-surface: #1e1e30;
  }
}

Die Schutzklausel :not([data-theme="light"]) stellt sicher, dass die explizite Auswahl eines Benutzers die Systemvorgabe überschreibt. Ohne sie würde die Media Query die manuelle Auswahl außer Kraft setzen.

Eine Anmerkung zu light-dark()

Die CSS-Funktion light-dark() ermöglicht es Ihnen, beide Theme-Werte inline zu deklarieren:

:root {
  color-scheme: light dark;
  --color-bg: light-dark(#ffffff, #161625);
}

Sie ist elegant für einfachere Token-Sets und funktioniert gut, wenn Sie nur zwei Themes benötigen. Bei Systemen mit mehr als zwei Themes oder Tokens, die über Farben hinausgehen, bietet der Ansatz mit Attributselektoren mehr Kontrolle.

Beachten Sie, dass light-dark() voraussetzt, dass color-scheme mit beiden Werten gesetzt ist, damit die Funktion korrekt aufgelöst wird. Die Unterstützung in modernen Browsern ist solide, aber noch relativ neu (Chrome 123+, Safari 17.5+, Firefox 120+). Prüfen Sie daher die Kompatibilität mit Ihrer Zielgruppe, bevor Sie sich darauf verlassen.

JavaScript für persistente manuelle Steuerung hinzufügen

Die Erkennung der Systemeinstellung erfolgt rein über CSS, aber das Speichern einer manuellen Benutzerentscheidung erfordert JavaScript. Hier ist eine minimale, produktionsreife Implementierung:

const STORAGE_KEY = 'theme-preference';
const root = document.documentElement;

function getSystemTheme() {
  return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
}

function getSavedTheme() {
  try {
    return localStorage.getItem(STORAGE_KEY);
  } catch {
    return null;
  }
}

function applyTheme(theme) {
  root.setAttribute('data-theme', theme);

  try {
    localStorage.setItem(STORAGE_KEY, theme);
  } catch {
    // Storage unavailable — theme still applies for this session
  }
}

// On load: respect saved preference, fall back to system
applyTheme(getSavedTheme() || getSystemTheme());

// Wire up your toggle button
document.getElementById('theme-toggle').addEventListener('click', () => {
  const current = root.getAttribute('data-theme');
  applyTheme(current === 'dark' ? 'light' : 'dark');
});

// Sync with OS changes when no manual preference is set
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => {
  if (!getSavedTheme()) {
    root.setAttribute('data-theme', e.matches ? 'dark' : 'light');
  }
});

Die try/catch-Blöcke um localStorage behandeln den privaten Browsing-Modus und Fehler beim Speicherkontingent zuverlässig – das Theme wird auch dann für die aktuelle Sitzung angewendet, wenn es nicht gespeichert werden kann.

Das kurzzeitige Aufblitzen des falschen Themes verhindern

Wenn Ihr JavaScript erst nach dem Rendern der Seite geladen wird, sehen Benutzer kurz das Standard-Theme, bevor das korrekte angewendet wird. Beheben Sie dies mit einem kleinen blockierenden Inline-Skript im <head>:

<script>
  try {
    const saved = localStorage.getItem('theme-preference');

    if (saved) {
      document.documentElement.setAttribute('data-theme', saved);
    } else if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
      document.documentElement.setAttribute('data-theme', 'dark');
    }
  } catch {
    if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
      document.documentElement.setAttribute('data-theme', 'dark');
    }
  }
</script>

Dieses Skript wird synchron ausgeführt, bevor irgendein Inhalt gerendert wird, und eliminiert so das Aufblitzen. Platzieren Sie es so früh wie möglich im <head>, noch vor allen Stylesheets, damit das Attribut bereits existiert, wenn CSS zum ersten Mal angewendet wird.

Das Muster, das skaliert

CSS-Variablen schaffen eine saubere Trennung zwischen Ihren Design-Tokens und Ihren Komponenten-Styles. Komponenten referenzieren Variablen; Themes definieren Variablen. Ein drittes Theme hinzuzufügen – etwa hoher Kontrast, eine Markenvariante oder eine saisonale Farbpalette – bedeutet lediglich, einen neuen Attributselektor-Block im CSS hinzuzufügen sowie eine neue Option in der Switcher-Logik. Sonst ändert sich nichts.

Fazit

Der eigentliche Mehrwert liegt hier nicht nur im Dark Mode, sondern in einer Theming-Architektur, die mit dem Wachstum Ihres Design-Systems wartbar bleibt. Indem Sie CSS-Variablen als Design-Tokens behandeln, sowohl System- als auch Benutzereinstellungen respektieren und die Sonderfälle (Speicherfehler, Render-Flashes, Änderungen auf Betriebssystemebene) berücksichtigen, schaffen Sie einen Switcher, der sich nativ in die Plattform einfügt und trivial erweiterbar ist. Das Muster ist klein, der Nutzen wächst mit jedem neuen Theme oder jeder neuen Komponente, die Sie hinzufügen.

FAQs

Beides funktioniert, aber ein data-theme-Attribut ist in der Regel sauberer, weil es einen einzelnen Wert ausdrückt statt einer Liste von Modifikatoren. Klassen eignen sich besser für kompositorische Zustände, in denen mehrere Flags gleichzeitig existieren. Bei Themes gibt es typischerweise jeweils nur einen aktiven Wert, was natürlicher zur Semantik eines Attributselektors passt.

Dieses Aufblitzen entsteht, wenn Ihr JavaScript erst ausgeführt wird, nachdem der Browser die Seite bereits mit den Standardstilen gerendert hat. Die Lösung ist ein kleines synchrones Inline-Skript im Head, das die gespeicherte Einstellung aus dem localStorage liest und das data-theme-Attribut setzt, bevor irgendein Rendering stattfindet. Für Erstbesucher können Sie zusätzlich prefers-color-scheme prüfen, damit das initiale Rendering sofort zum System-Theme passt.

Ja. CSS-Variablen können jeden gültigen CSS-Wert enthalten, einschließlich Abstandsskalen, Border-Radii, Schatten, Font-Stacks und Animationsdauern. Dadurch sind sie nützlich, um etwa Dichte (kompakte versus komfortable Layouts), Bewegungseinstellungen oder auch typografische Varianten zu themen. Behandeln Sie Variablen umfassend als Design-Tokens, nicht nur als Farbreferenzen.

Es funktioniert gut in modernen Browsern (Chrome 123+, Safari 17.5+, Firefox 120+), wird jedoch in älteren Versionen nicht unterstützt. Für Projekte mit einer modernen Zielgruppe ist es eine saubere Wahl. Für eine breitere Kompatibilität bleibt der Ansatz mit data-theme-Attribut sicherer und skaliert zudem auf mehr als zwei Themes, was light-dark() allein nicht leisten kann.

Truly understand users experience

See every user interaction, feel every frustration and track all hesitations with OpenReplay — the open-source digital experience platform. It can be self-hosted in minutes, giving you complete control over your customer data. . Check our GitHub repo and join the thousands of developers in our community..

OpenReplay