Dark Mode beginnt in den meisten Projekten mit einer Verdopplung: ein vollständiger Satz Farbvariablen für das helle Theme, ein zweiter für das dunkle, definiert in einer eigenen prefers-color-scheme-Media-Query oder unter einer .dark-Klasse noch einmal komplett wiederholt. light-dark() legt beide Werte stattdessen direkt nebeneinander, an genau der Stelle, an der die Farbe gebraucht wird.
light-dark() nimmt zwei Farbwerte entgegen. Der Browser gibt den ersten zurück, wenn Light Mode aktiv ist oder keine Präferenz vorliegt, den zweiten bei Dark Mode. Damit das funktioniert, muss irgendwo im Dokument color-scheme gesetzt sein und beide Schemata einschließen.
:root {
color-scheme: light dark;
}
body {
background: light-dark(#ffffff, #111827);
color: light-dark(#111827, #f3f4f6);
}
Kein doppelter Variablenblock, keine Media-Query. Hell- und Dunkelwert stehen in derselben Zeile, an derselben Stelle im Code, an der auch die Farbe verwendet wird.
color-scheme: light dark erlaubt beide Modi, je nachdem, was Betriebssystem oder Nutzende einstellen. Ohne diese Deklaration bleibt light-dark() wirkungslos und liefert immer den ersten Wert.
Schon color-scheme allein bringt einen Effekt, den man leicht übersieht: Checkboxen, Radio-Buttons, Scrollbalken und Datumsfelder übernehmen automatisch das passende Systemtheme, ganz ohne eigene Farbdefinition. Der Browser kennt seine eigenen Bedienelemente bereits in beiden Varianten.
Die automatische Kopplung an die Systemeinstellung reicht nicht, sobald Nutzende das Theme unabhängig vom Betriebssystem selbst wählen sollen. Der Haken dabei: Eine Klasse wie .theme-dark auf <html> zu setzen genügt allein nicht. Ohne ein eigenes color-scheme auf genau dieser Klasse bleibt der Browser bei der Systemeinstellung, unabhängig davon, welche Klasse gerade aktiv ist.
:root {
color-scheme: light dark; /* folgt automatisch dem System */
}
html.theme-light {
color-scheme: light; /* erzwingt Light, unabhängig vom System */
}
html.theme-dark {
color-scheme: dark; /* erzwingt Dark, unabhängig vom System */
}
Den eigentlichen Umschalter übernimmt eine kleine Funktion, die die Klasse setzt und die Wahl speichert.
const root = document.documentElement;
function setTheme(mode) {
root.classList.remove('theme-light', 'theme-dark');
if (mode !== 'system') {
root.classList.add(`theme-${mode}`);
}
localStorage.setItem('theme', mode);
}
// Beim Laden: gespeicherte Präferenz übernehmen, sonst dem System folgen
setTheme(localStorage.getItem('theme') || 'system');
Ja, beide Fälle sind unproblematisch. light-dark() verlangt an beiden Stellen nur einen gültigen <color>-Wert, und sowohl eine Custom Property als auch das Ergebnis von color-mix() liefern genau das.
:root {
color-scheme: light dark;
--brand-light: #22d3ee;
--brand-dark: #0e7490;
}
.button {
background: light-dark(var(--brand-light), var(--brand-dark));
}
Auch verschachtelt funktioniert es:
.button {
background: light-dark(
color-mix(in oklch, #22d3ee, white 20%),
color-mix(in oklch, #0e7490, black 20%)
);
}
Für beide Kombinationen gibt es keine dokumentierten Einschränkungen. Da color-mix() und light-dark() beide vergleichsweise jung sind, lohnt sich vor dem produktiven Einsatz trotzdem ein kurzer Test in den Zielbrowsern.
Seit einer Spezifikations-Erweiterung nimmt light-dark() neben zwei Farben auch zwei <image>-Werte entgegen, und ein Gradient zählt in CSS als Bild. Auf Eigenschaften wie background-image funktioniert damit derselbe Umschaltmechanismus wie bei Farben, nur eben mit ganzen Verläufen.
.blob {
background-image: light-dark(
linear-gradient(135deg, #22d3ee, #6366f1),
linear-gradient(135deg, #0e7490, #312e81)
);
}
Eine feste Regel dabei: Ein einzelner light-dark()-Aufruf nimmt entweder zwei Farben oder zwei Bilder entgegen, nie gemischt. Der Grund liegt im CSS-Parser, der den Werttyp schon beim Parsen kennen muss. Deshalb funktioniert der Bild-Aufruf auch nur an Stellen, die <image> erwarten, etwa background-image oder mask-image, nicht an der Kurzschreibweise background und nicht bei color.
Der Support ist frisch: Chrome 150 und Firefox 150 unterstützen die Bild-Variante seit Mitte Juni 2026, Safari bislang nicht (WebKit-Bug 309689 ist offen). Für den produktiven Einsatz empfiehlt sich eine Absicherung per Feature-Query.
@supports (background-image: light-dark(none, none)) {
/* moderner Bildwechsel */
}
Die Farb-Variante von light-dark() gilt bereits seit Mai 2024 als „Newly Available“ (Chrome 123, Firefox 120, Safari 17.5) und erreicht „Widely Available“ voraussichtlich im November 2026. Die Bild-Variante steht also noch am Anfang dieses Reifeprozesses. Damit deckt eine einzige Funktion inzwischen Text- und Hintergrundfarben, Verläufe und Bilder mit demselben Prinzip ab.
| Technik | Zweck |
|---|---|
| color-scheme: light dark auf :root | Systemtheme aktivieren, native Bedienelemente automatisch anpassen |
| light-dark(hell, dunkel) | zwei Farbwerte an einer Stelle definieren, statt zwei Variablensätzen |
| color-scheme: light / dark auf einer Umschalt-Klasse | manuelle Auswahl erzwingen, unabhängig vom System |
| kleine JS-Funktion mit localStorage | Wahl der Nutzenden merken und beim nächsten Besuch anwenden |
| light-dark() mit var() oder verschachteltem color-mix() | berechnete oder wiederverwendete Farbwerte pro Theme, ohne Einschränkung |
| light-dark() mit zwei <image>-Werten (z. B. Gradients) | ganze Verläufe oder Bilder je Theme wechseln (Chrome/Firefox 150, noch nicht Safari) |
CSS-Farbfunktion, die zwei Farbwerte entgegennimmt und je nach aktivem Farbschema den passenden zurückgibt. Benötigt ein aktives color-scheme auf demselben Element oder einem Vorfahren.
CSS-Eigenschaft, die festlegt, welche Farbschemata ein Element unterstützt. Steuert sowohl light-dark() als auch das Erscheinungsbild nativer Formularelemente und Scrollbalken.
Media Feature, das die Systempräferenz für Light oder Dark Mode abfragt. Älterer, media-query-basierter Ansatz, weiterhin nötig für Werte jenseits von Farben, etwa Bilder.
Klassifizierung der Web-Platform-Initiative für den Reifegrad einer Web-Technologie über alle großen Browser-Engines hinweg, von „Newly Available“ bis „Widely Available“.