Tutorials

CSS light-dark()-Funktion: Natives Theme-Switching

8 Min. Lesezeit

Die Implementierung des Dunkelmodus erforderte historisch eine nicht triviale Menge an Boilerplate: eine prefers-color-scheme-Media-Query, die jede Custom Property überschreibt, JavaScript für Benutzer-Toggles, localStorage zur Persistenz von Präferenzen und ein Inline-Skript im <head>, um den Flash des falschen Themes beim Seitenaufruf zu verhindern. Die CSS-Funktion light-dark() eliminiert das alles nicht, reduziert aber die CSS-Angriffsfläche des Problems erheblich.

light-dark() ist eine CSS-Farbfunktion, die genau zwei Farbwerte annimmt und den ersten zurückgibt, wenn das aktive Farbschema hell ist, oder den zweiten, wenn es dunkel ist. Sie ist das semantische CSS-Äquivalent des ternären Operators für Farben.

Was ist light-dark()?

Die Funktionssignatur ist einfach:

color: light-dark(<light-color>, <dark-color>);

Wenn das aktive Farbschema hell ist, verwendet der Browser <light-color>. Wenn es dunkel ist, verwendet er <dark-color>. Das „aktive Farbschema" wird durch die CSS-Eigenschaft color-scheme bestimmt, die wiederum auf die System-Media-Query prefers-color-scheme oder einen explizit auf einem Element gesetzten Wert reagiert.

Die Funktion wird unterstützt in:

  • Chrome/Edge: seit Version 123 (März 2024)
  • Firefox: seit Version 120 (November 2023)
  • Safari: seit Version 17.5 (Juni 2024)

Die globale Unterstützung liegt Anfang 2026 bei etwa 85%. Es ist eine relativ neue Ergänzung, aber die Browser-Abdeckung wächst schnell genug, um sie mit einer Fallback-Strategie in der Produktion zu verwenden.

Funktionsweise mit der color-scheme-Eigenschaft

light-dark() funktioniert nicht isoliert. Es hängt vollständig davon ab, dass die CSS-Eigenschaft color-scheme korrekt gesetzt ist. Ohne sie hat die Funktion keinen Kontext, um zu entscheiden, welchen Wert sie zurückgeben soll.

Die Eigenschaft color-scheme deklariert, welche Farbschemata ein Dokument oder Element unterstützt. Das Setzen auf :root ist der Ausgangspunkt:

:root {
  color-scheme: light dark;
}

Diese einzelne Deklaration teilt dem Browser mit, dass Ihre Seite sowohl helle als auch dunkle Farbschemata unterstützt. Der Browser liest dann:

  1. Die prefers-color-scheme-Systemeinstellung des Benutzers
  2. Wendet das entsprechende Schema an
  3. Lässt alle light-dark()-Aufrufe auf der Seite zum entsprechenden Wert auflösen

Damit gesetzt, wird das Definieren von theme-bewussten Farben zu einer Frage einzelner Deklarationen:

:root {
  color-scheme: light dark;

  --color-background: light-dark(#FFFFFF, #0F0F17);
  --color-text:       light-dark(#1A1A2E, #E8E8F0);
  --color-border:     light-dark(#DEE2E6, #2E2E4A);
  --color-accent:     light-dark(#2563EB, #60A5FA);
}

Keine Media Query. Keine Selektor-Überschreibungen. Eine Deklaration pro Farbe, beide Werte inline. Der Browser übernimmt das Switching automatisch basierend auf der Systempräferenz.

Auf ein einzelnes Schema beschränken

Das Setzen von color-scheme: light oder color-scheme: dark erzwingt unabhängig von der Systempräferenz ein einzelnes Schema:

/* Immer hell, unabhängig von der Betriebssystemeinstellung */
.widget {
  color-scheme: light;
  background: light-dark(#FFFFFF, #0F0F17);
  /* Löst immer zu #FFFFFF auf */
}

/* Immer dunkel */
.dark-panel {
  color-scheme: dark;
  color: light-dark(#1A1A2E, #E8E8F0);
  /* Löst immer zu #E8E8F0 auf */
}

Das ist nützlich für UI-Komponenten, die immer in einem bestimmten Modus erscheinen sollen – zum Beispiel ein Code-Editor, der immer einen dunklen Hintergrund haben soll, unabhängig vom umgebenden Seitenthema.

Das only-Keyword

Das Hinzufügen von only verhindert, dass die Kaskade das Schema für dieses Element überschreibt:

.forced-light {
  color-scheme: only light;
}

Das ist hauptsächlich nützlich, wenn Sie ein Element in einem Dunkelmodus-Kontext haben, das hell bleiben muss.

Ersetzen von prefers-color-scheme-Media-Queries

Der traditionelle Ansatz zum Dunkelmodus mit Media Queries erfordert das Duplizieren oder Überschreiben jeder Farbvariable:

/* Traditioneller Ansatz — ausführlich */
:root {
  --bg: #FFFFFF;
  --text: #1A1A2E;
  --accent: #2563EB;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #0F0F17;
    --text: #E8E8F0;
    --accent: #60A5FA;
  }
}

Mit light-dark() kollabiert das zu:

/* light-dark()-Ansatz — eine Deklaration pro Variable */
:root {
  color-scheme: light dark;

  --bg:     light-dark(#FFFFFF, #0F0F17);
  --text:   light-dark(#1A1A2E, #E8E8F0);
  --accent: light-dark(#2563EB, #60A5FA);
}

Die Variablen selbst werden selbstbeschreibend. Wenn Sie --accent: light-dark(#2563EB, #60A5FA) lesen, sehen Sie sofort beide Werte und verstehen die Beziehung. Der Media-Query-Ansatz verteilt hell und dunkel Werte über zwei separate Blöcke, was das Auditieren und Aktualisieren der Palette schwieriger macht.

Wann Media Queries noch benötigt werden

Die prefers-color-scheme-Media-Query bleibt für Nicht-Farb-Anpassungen notwendig, die sich je nach Theme ändern:

@media (prefers-color-scheme: dark) {
  /* Nicht-Farb-Anpassungen, die light-dark() nicht ausdrücken kann */
  img.logo {
    filter: invert(1) brightness(1.2);
  }

  .hero-image {
    opacity: 0.85;
  }
}

Für alles, was eine reine Farbänderung ist, ist light-dark() sauberer. Für strukturelle oder nicht-farbige Anpassungen (Bildfilter, Deckkraft, Display-Eigenschaften) bleibt die Media Query das richtige Werkzeug.

Kombination mit CSS Custom Properties

light-dark() funktioniert innerhalb von Custom Property-Werten, wo seine volle Kraft entfaltet wird. Sie definieren alle theme-bewussten Farben bei :root, und jede Komponente der Seite referenziert diese Variablen. Wenn sich das Farbschema ändert, wird alles gleichzeitig aktualisiert.

Vollständiges Theme-System-Beispiel

:root {
  color-scheme: light dark;

  /* Hintergründe */
  --color-bg-base:      light-dark(#FFFFFF,   #0F0F17);
  --color-bg-elevated:  light-dark(#F8F9FA,   #1A1A2E);
  --color-bg-overlay:   light-dark(#F1F3F5,   #252540);

  /* Text */
  --color-text-primary:   light-dark(#1A1A2E, #E8E8F0);
  --color-text-secondary: light-dark(#4A4A6A, #A8A8C0);
  --color-text-muted:     light-dark(#6C757D, #6A6A88);
  --color-text-inverse:   light-dark(#FFFFFF, #1A1A2E);

  /* Rahmen */
  --color-border:         light-dark(#DEE2E6, #2E2E4A);
  --color-border-strong:  light-dark(#ADB5BD, #4A4A6A);

  /* Interaktiv / Marke */
  --color-accent:         light-dark(#2563EB, #60A5FA);
  --color-accent-hover:   light-dark(#1D4ED8, #93C5FD);
  --color-accent-subtle:  light-dark(#DBEAFE, #1E3A5F);

  /* Feedback */
  --color-success:  light-dark(#16A34A, #4ADE80);
  --color-warning:  light-dark(#D97706, #FCD34D);
  --color-danger:   light-dark(#DC2626, #F87171);

  --color-success-bg: light-dark(#F0FDF4, #052E16);
  --color-warning-bg: light-dark(#FFFBEB, #2D1A00);
  --color-danger-bg:  light-dark(#FEF2F2, #2D0A0A);
}

Komponenten referenzieren diese Variablen, ohne etwas über Theming wissen zu müssen:

.card {
  background: var(--color-bg-elevated);
  border: 1px solid var(--color-border);
  color: var(--color-text-primary);
}

.btn-primary {
  background: var(--color-accent);
  color: var(--color-text-inverse);
}

.btn-primary:hover {
  background: var(--color-accent-hover);
}

.alert-success {
  background: var(--color-success-bg);
  color: var(--color-success);
  border-left: 3px solid var(--color-success);
}

Beachten Sie, dass der helle Akzent #2563EB im Dunkelmodus zu #60A5FA wechselt. Das ist beabsichtigt – das 600-Gewicht-Blau besteht WCAG AA Kontrast gegen Weiß, versagt aber gegen dunkle Hintergründe. Das 400-Gewicht macht die Farbe hell genug, um barrierefreien Kontrast auf dunklen Oberflächen zu gewährleisten. Verwenden Sie den Kontrast-Prüfer, um jede Kombination zu verifizieren, und den Schattengenerator, um den richtigen Farbton für jeden Modus zu finden.

light-dark() in andere Funktionen verschachteln

light-dark() gibt einen Farbwert zurück, daher kann es überall dort verwendet werden, wo eine Farbe gültig ist – einschließlich innerhalb anderer Funktionen:

:root {
  color-scheme: light dark;
  --brand: #3B82F6;

  /* light-dark() innerhalb von color-mix() verwenden */
  --brand-surface: color-mix(
    in oklch,
    var(--brand) 15%,
    light-dark(white, #09090b)
  );
}

Das erzeugt eine Oberflächenfarbe, die ein 15%iger Ton der Markenfarbe ist, gemischt mit Weiß im Hellmodus und Beinahe-Schwarz im Dunkelmodus – automatisch theme-bewusst.

Benutzer-Toggle hinzufügen (JavaScript)

Auf die Systempräferenz zu reagieren ist die richtige Standardvorgabe, aber Benutzer sollten sie überschreiben können. Das erfordert JavaScript, um ihre Wahl zu persistieren und den Browser-Standard zu überschreiben.

color-scheme mit JavaScript steuern

Der entscheidende Einblick ist, dass color-scheme eine CSS-Eigenschaft ist, die über JavaScript gesetzt werden kann:

// Das Farbschema programmgesteuert setzen
document.documentElement.style.colorScheme = 'dark';
document.documentElement.style.colorScheme = 'light';

// Die Überschreibung entfernen (kehrt zur Systempräferenz zurück)
document.documentElement.style.colorScheme = '';

Wenn Sie color-scheme über den Inline-Stil des Root-Elements setzen, überschreibt das die Stylesheet-Deklaration. Alle light-dark()-Werte werden neu zur entsprechenden Variante aufgelöst.

Vollständige Toggle-Implementierung

const STORAGE_KEY = 'color-scheme-preference';

function initColorScheme() {
  const stored = localStorage.getItem(STORAGE_KEY);
  if (stored === 'light' || stored === 'dark') {
    document.documentElement.style.colorScheme = stored;
  }
  // Wenn keine gespeicherte Präferenz, wird CSS color-scheme: light dark; über OS-Präferenz verwaltet
}

function toggleColorScheme() {
  const current = getComputedStyle(document.documentElement)
    .colorScheme
    .trim();

  // Nächsten Wert bestimmen
  const next = current.includes('dark') ? 'light' : 'dark';

  document.documentElement.style.colorScheme = next;
  localStorage.setItem(STORAGE_KEY, next);

  // Etwaigen Toggle-Button-Zustand aktualisieren
  updateToggleButton(next);
}

function updateToggleButton(scheme) {
  const btn = document.getElementById('theme-toggle');
  if (!btn) return;
  btn.setAttribute('aria-label',
    scheme === 'dark' ? 'Zum Hellmodus wechseln' : 'Zum Dunkelmodus wechseln'
  );
  btn.dataset.scheme = scheme;
}

// Vor dem ersten Rendern ausführen, um Flash zu vermeiden
initColorScheme();

// Nach dem Laden des DOM an den Toggle-Button anhängen
document.addEventListener('DOMContentLoaded', () => {
  document.getElementById('theme-toggle')
    ?.addEventListener('click', toggleColorScheme);
});

Das Aufrufen von initColorScheme() bevor das DOM vollständig geparst ist, ist entscheidend. Wenn es zu spät läuft, sehen Benutzer mit gespeicherter Präferenz kurz das OS-Standard-Theme, bevor es wechselt – das klassische „Flash des falschen Themes". Platzieren Sie dieses Skript inline im <head> oder verwenden Sie das defer-Attribut sorgfältig (beachten Sie, dass defer-Skripte nach dem DOM-Parsen ausgeführt werden, was möglicherweise zu spät ist).

Das Anti-Flash-Muster

Der robusteste Anti-Flash-Ansatz führt ein minimales Inline-Skript im <head> aus:

<head>
  <meta name="color-scheme" content="light dark">
  <script>
    const stored = localStorage.getItem('color-scheme-preference');
    if (stored) {
      document.documentElement.style.colorScheme = stored;
    }
  </script>
  <link rel="stylesheet" href="styles.css">
</head>

Dieses Skript läuft synchron, bevor CSS angewendet wird, sodass der Browser den korrekten color-scheme-Wert beim ersten Rendern berechnet. Das <meta name="color-scheme">-Tag teilt dem Browser mit, welche Schemas erwartet werden, noch bevor CSS geparst wird – das verhindert in einigen Browsern einen kurzen weißen Flash auf Dunkelmodus-Seiten.

Migrationsleitfaden von JS-basierten Themes

Viele bestehende Dunkelmodus-Implementierungen verwenden ein data-theme-Attribut, das von JavaScript umgeschaltet wird, mit CSS-Überschreibungen in [data-theme="dark"]. Die Migration zu light-dark() ist inkrementell – Sie müssen nicht alles auf einmal ändern.

Schritt 1: color-scheme zu :root hinzufügen

:root {
  color-scheme: light dark;
  /* Bestehende Custom Properties bleiben unverändert */
}

Schritt 2: Variablen einzeln migrieren

Beginnen Sie mit einer einzelnen Variable als Proof of Concept. Ersetzen Sie das geteilte Deklarationsmuster durch ein einheitliches light-dark():

/* Vorher */
:root {
  --bg: #FFFFFF;
}
[data-theme="dark"] {
  --bg: #0F0F17;
}

/* Nachher */
:root {
  color-scheme: light dark;
  --bg: light-dark(#FFFFFF, #0F0F17);
}

Schritt 3: Den Toggle aktualisieren

Ändern Sie den JavaScript-Toggle vom Setzen von data-theme zum Setzen von style.colorScheme:

/* Vorher */
document.documentElement.setAttribute('data-theme', scheme);

/* Nachher */
document.documentElement.style.colorScheme = scheme;

Schritt 4: data-theme-Selektoren entfernen

Sobald alle Variablen migriert sind, entfernen Sie die [data-theme="dark"]-CSS-Blöcke.

Beide Systeme während der Migration aufrechterhalten

Sie können beide Systeme gleichzeitig betreiben. Behalten Sie [data-theme="dark"]-Überschreibungen für noch nicht migrierte Variablen. Neue Variablen verwenden light-dark(). Der JavaScript-Toggle setzt während der Übergangsphase sowohl data-theme als auch style.colorScheme.

Browser-Unterstützungs-Fallback

Für die ungefähr 15% der Browser ohne light-dark()-Unterstützung geben Sie explizite Fallbacks an:

:root {
  /* Fallback: explizite Hellmodus-Werte */
  --color-bg: #FFFFFF;
  --color-text: #1A1A2E;

  /* Progressive Enhancement mit light-dark() */
  --color-bg:   light-dark(#FFFFFF, #0F0F17);
  --color-text: light-dark(#1A1A2E, #E8E8F0);
}

/* Fallback-Dunkelmodus für Browser ohne light-dark() */
@supports not (color: light-dark(white, black)) {
  @media (prefers-color-scheme: dark) {
    :root {
      --color-bg: #0F0F17;
      --color-text: #E8E8F0;
    }
  }
}

Der @supports not (color: light-dark(white, black))-Block gilt nur für Browser, die light-dark() nicht verstehen. Moderne Browser überspringen ihn vollständig, da die negative Bedingung falsch ist.

Wesentliche Erkenntnisse

  • light-dark(<light-value>, <dark-value>) gibt das erste Argument in einem hellen Farbschema und das zweite in einem dunklen Schema zurück. Es ist die CSS-native Art, „diese Farbe, angepasst an das aktuelle Theme" auszudrücken.
  • Es hängt davon ab, dass die CSS-Eigenschaft color-scheme auf dem Element (oder einem Vorfahren) gesetzt ist. Setzen Sie immer color-scheme: light dark auf :root, um die automatische Anpassung über prefers-color-scheme zu aktivieren.
  • Der Hauptvorteil gegenüber dem traditionellen Media-Query-Ansatz ist das Zusammenführen beider Theme-Werte in einer einzigen Deklaration – was die Beziehung zwischen hell und dunkel Varianten explizit und prüfbar macht.
  • Benutzerüberschreibungen erfordern das Setzen von document.documentElement.style.colorScheme via JavaScript. Persistieren Sie die Wahl in localStorage und wenden Sie sie in einem Inline-<head>-Skript vor dem Laden von CSS an, um Flash zu verhindern.
  • Die Migration von data-theme-attributbasierten Systemen ist inkrementell – verschieben Sie eine Variable nach der anderen vom [data-theme="dark"]-Überschreibungsmuster zum light-dark()-Inline-Muster.
  • Die Browser-Unterstützung liegt 2026 bei ~85%. Geben Sie einen @supports not-Fallback mit einem @media (prefers-color-scheme: dark)-Block für ältere Umgebungen an.
  • Verwenden Sie den Kontrast-Prüfer, um zu verifizieren, dass sowohl die hell- als auch die dunkelmodus-Farbwerte in jedem light-dark()-Paar die WCAG-Kontrastanforderungen gegen ihre jeweiligen Hintergründe erfüllen, und den Schattengenerator, um den richtigen Farbton jeder Farbe für jeden Modus zu finden.

Ähnliche Farben

Ähnliche Marken

Ähnliche Werkzeuge