Farbwerte direkt abhängig vom Farbschema definieren, ohne Media Query
Mit der Funktion light-dark() lässt sich ein Hell- und ein Dunkelmodus-Farbwert in einer einzigen Deklaration angeben, statt zwei getrennte prefers-color-scheme-Blöcke zu pflegen. Der Browser wählt automatisch anhand des aktuell aktiven color-scheme, welcher der beiden Werte zum Einsatz kommt, sowohl bei System-Präferenz als auch bei einem manuellen Umschalter, der die Eigenschaft überschreibt.
Inhaltsverzeichnis
- 1. Das Grundprinzip: ein Wertepaar statt zwei getrennter Regelblöcke
- 2. color-scheme: die Voraussetzung, damit light-dark() überhaupt greift
- 3. Vergleich zu prefers-color-scheme-Media-Queries
- 4. Manueller Dark-Mode-Umschalter mit color-scheme überschreiben
- 5. light-dark() mit Custom Properties für Design-Token-Systeme
- 6. Browser-Support und Absicherung mit @supports
- 7. Wann sich der Umstieg von Media Queries auf light-dark() lohnt
- 8. Praxisbeispiel: ein vollständiges Farbschema für eine Komponente
- 9. Barrierefreiheit: Kontrastprüfung für beide Modi getrennt durchführen
- 10. Zusammenfassung
- 11. FAQ
1. Das Grundprinzip: ein Wertepaar statt zwei getrennter Regelblöcke
Die Funktion light-dark(<hell>, <dunkel>) nimmt genau zwei Farbwerte entgegen: den ersten für den hellen Modus, den zweiten für den dunklen. Welcher der beiden Werte tatsächlich angewendet wird, entscheidet der Browser anhand des aktuell wirksamen color-scheme, das entweder über die CSS-Eigenschaft color-scheme auf einem Vorfahren-Element oder über die System-Präferenz prefers-color-scheme gesetzt sein kann.
Damit verschmilzt light-dark() zwei bisher getrennte Konzepte in einer Deklaration: die eigentliche Farbdefinition und die Bedingung, wann welche Farbe gilt. Das reduziert Wiederholung in Stylesheets erheblich, weil nicht mehr für jede einzelne Eigenschaft ein eigener prefers-color-scheme: dark-Block nötig ist, sondern die Fallunterscheidung direkt im Wert selbst steckt.
:root {
color-scheme: light dark;
}
body {
/* Ein Wertepaar statt zweier Media-Query-Bloecke */
background: light-dark(#ffffff, #0f172a);
color: light-dark(#1e293b, #e2e8f0);
}
2. color-scheme: die Voraussetzung, damit light-dark() überhaupt greift
Damit light-dark() funktioniert, muss auf dem Root-Element oder einem Vorfahren die Eigenschaft color-scheme: light dark; gesetzt sein. Diese Deklaration teilt dem Browser mit, dass das Dokument beide Modi unterstützt, und aktiviert erst dadurch die automatische Auswahl über light-dark(). Fehlt diese Deklaration, verhält sich light-dark() stets so, als wäre nur der helle Modus aktiv, unabhängig von der System-Präferenz des Nutzers.
Diese Kopplung ist kein Zufall: color-scheme steuert nicht nur light-dark(), sondern auch native Browser-UI-Elemente wie Formularfelder, Scrollbars und die Systemfarbe der Adressleiste auf mobilen Geräten. Wer color-scheme ohnehin für ein konsistentes natives Erscheinungsbild setzt, bekommt light-dark() quasi kostenlos als zusätzliche Fähigkeit dazu.
3. Vergleich zu prefers-color-scheme-Media-Queries
Der klassische Ansatz für Dunkelmodus-Unterstützung besteht aus einem Media-Query-Block, der alle betroffenen Eigenschaften innerhalb von @media (prefers-color-scheme: dark) { ... } neu deklariert. Das funktioniert zuverlässig, führt aber bei größeren Stylesheets dazu, dass dieselben Selektoren zweimal existieren, einmal für den Standardfall und einmal für den Dunkelmodus, was Wartung und Konsistenzprüfung erschwert, sobald ein Farbwert im einen Block geändert wird, aber im anderen vergessen wird.
Mit light-dark() steht die Fallunterscheidung direkt neben der Eigenschaft, für die sie gilt, wodurch beide Zustände beim Lesen des Codes sofort sichtbar sind und nicht über zwei entfernte Codestellen verteilt sind. Das senkt das Risiko, eine Farbe im Dunkelmodus zu vergessen, spürbar, weil es keinen zweiten Block mehr gibt, den man vergessen könnte.
/* Klassischer Ansatz: zwei getrennte Bloecke */
.card {
background: #ffffff;
border-color: #e2e8f0;
}
@media (prefers-color-scheme: dark) {
.card {
background: #1e293b;
border-color: #334155;
}
}
/* Mit light-dark(): ein einziger Block */
.card {
background: light-dark(#ffffff, #1e293b);
border-color: light-dark(#e2e8f0, #334155);
}
4. Manueller Dark-Mode-Umschalter mit color-scheme überschreiben
Ein häufiges Anforderungsmuster ist ein Umschalter in der Benutzeroberfläche, der es Nutzern erlaubt, den Dunkelmodus unabhängig von der System-Präferenz manuell zu erzwingen. Weil light-dark() sich strikt am wirksamen color-scheme-Wert orientiert, reicht es, diese Eigenschaft per JavaScript auf dem Root-Element zu setzen, etwa light oder dark statt light dark, um die automatische System-Erkennung zu übersteuern.
Der Vorteil gegenüber dem klassischen Media-Query-Ansatz ist, dass für diesen manuellen Umschalter keine zusätzliche CSS-Klasse und kein zweites Set an Selektoren nötig ist. Eine einzige JavaScript-Zeile, die color-scheme auf dem html-Element setzt, reicht aus, damit sämtliche light-dark()-Aufrufe im gesamten Stylesheet sofort den gewünschten Modus anzeigen.
<script type="text/plain">
// Nutzer waehlt manuell den Dunkelmodus, unabhaengig vom System
document.documentElement.style.colorScheme = 'dark';
// Zurueck zur System-Praeferenz
document.documentElement.style.colorScheme = 'light dark';
</script>
5. light-dark() mit Custom Properties für Design-Token-Systeme
In größeren Projekten mit einem eigenen Design-Token-System lässt sich light-dark() direkt bei der Definition der Custom Properties einsetzen, statt es an jeder einzelnen Verwendungsstelle zu wiederholen. Das zentralisiert die Farbschema-Logik an einer einzigen Stelle im Stylesheet und macht jede weitere Verwendung der Custom Property automatisch farbschema-fähig, ohne dass der aufrufende Code davon wissen muss.
Diese Zentralisierung zahlt sich besonders aus, wenn ein Token wie --surface-primary an vielen verschiedenen Stellen im Projekt verwendet wird. Ändert sich später die konkrete Dunkelmodus-Farbe, reicht eine einzige Änderung an der Token-Definition, statt Dutzende Verwendungsstellen im gesamten Projekt zu durchsuchen und einzeln anzupassen.
:root {
color-scheme: light dark;
--surface-primary: light-dark(#ffffff, #0f172a);
--surface-secondary: light-dark(#f8fafc, #1e293b);
--text-primary: light-dark(#0f172a, #f1f5f9);
--border-subtle: light-dark(#e2e8f0, #334155);
}
.card {
background: var(--surface-primary);
color: var(--text-primary);
border: 1px solid var(--border-subtle);
}
6. Browser-Support und Absicherung mit @supports
light-dark() wird von allen aktuellen Versionen von Chrome, Edge, Firefox und Safari unterstützt, nachdem die Funktion 2023 in Safari, Chrome und Firefox nahezu zeitgleich landete. Für die meisten Projekte, die keine sehr alten Browser-Versionen mehr bedienen müssen, ist die Funktion damit ohne größere Einschränkung produktionsreif einsetzbar.
Für Projekte mit strengeren Kompatibilitätsanforderungen lässt sich der Support gezielt mit @supports (color: light-dark(#fff, #000)) prüfen. Innerhalb der Regel steht die moderne Deklaration, außerhalb ein klassischer, auf Media Queries basierender Fallback, der auch in älteren Browsern korrekt funktioniert.
7. Wann sich der Umstieg von Media Queries auf light-dark() lohnt
Der Umstieg lohnt sich besonders in Projekten mit vielen einzelnen Farbdeklarationen, die bisher über zahlreiche, verstreute prefers-color-scheme-Blöcke verteilt waren. Hier reduziert light-dark() nicht nur die Zeilenzahl im Stylesheet, sondern senkt auch das Risiko, dass ein Dunkelmodus-Wert bei einer Änderung schlicht vergessen wird, weil beide Werte untrennbar in derselben Zeile stehen.
Weniger sinnvoll ist der komplette Umstieg, wenn der Dunkelmodus mehr verändert als reine Farbwerte, etwa unterschiedliche Bildquellen, Schriftgewichte oder Layout-Eigenschaften. Für solche strukturellen Unterschiede bleibt ein klassischer prefers-color-scheme-Block das richtige Werkzeug, während light-dark() gezielt für reine Farbentscheidungen eingesetzt wird, häufig sogar kombiniert im selben Projekt.
8. Praxisbeispiel: ein vollständiges Farbschema für eine Komponente
Am Beispiel einer Karten-Komponente mit Rahmen, Schatten und einem Akzentfarbton zeigt sich das Muster in voller Breite: jede visuelle Eigenschaft, die sich zwischen hell und dunkel unterscheidet, bekommt ihr eigenes light-dark()-Wertepaar, während strukturelle Eigenschaften wie Padding oder Border-Radius unverändert bleiben, weil sie ohnehin unabhängig vom Farbschema sind.
Wichtig ist, auch Schattenfarben nicht zu vergessen: Ein Schatten, der im hellen Modus dezent grau wirkt, kann im dunklen Modus komplett unsichtbar werden, wenn er nicht ebenfalls über light-dark() angepasst wird. Ein dunklerer, aber intensiverer Schattenwert im Dunkelmodus sorgt dafür, dass die Tiefenwirkung der Komponente in beiden Modi erhalten bleibt.
.product-card {
background: light-dark(#ffffff, #1e293b);
border: 1px solid light-dark(#e2e8f0, #334155);
box-shadow: 0 4px 12px light-dark(rgba(15,23,42,0.08), rgba(0,0,0,0.4));
color: light-dark(#0f172a, #f1f5f9);
padding: 1.5rem;
border-radius: 0.75rem;
}
.product-card .price {
color: light-dark(#7c3aed, #c4b5fd);
}
9. Barrierefreiheit: Kontrastprüfung für beide Modi getrennt durchführen
Weil light-dark() beide Farbpaare so kompakt nebeneinander stellt, entsteht leicht der Eindruck, ein Wertepaar sei automatisch für beide Modi kontrastsicher, nur weil es syntaktisch zusammengehört. Tatsächlich müssen Kontrastverhältnisse für hellen und dunklen Modus unabhängig voneinander geprüft werden, denn ein Grauton, der im hellen Modus einen ausreichenden Kontrast zu Weiß bietet, kann im Dunkelmodus gegen einen dunklen Hintergrund deutlich zu schwach wirken.
Praktisch bedeutet das, jedes light-dark()-Paar mit einem Kontrast-Checker zweimal zu prüfen, einmal für die Kombination aus hellem Vordergrund- und Hintergrundwert, einmal für die dunkle Variante. Werkzeuge, die WCAG-Kontrastverhältnisse berechnen, sollten deshalb in den Review-Prozess für neue light-dark()-Deklarationen aufgenommen werden, nicht erst nachträglich beim fertigen Screenshot.
| Ansatz | Codezeilen pro Farbe | Risiko vergessener Dunkelmodus-Werte | Geeignet für |
|---|---|---|---|
@media (prefers-color-scheme: dark) |
2 (zwei Blöcke) | Höher, Werte örtlich getrennt | Strukturelle Unterschiede, nicht nur Farbe |
light-dark() pro Eigenschaft |
1 | Niedriger, Werte in einer Zeile | Einzelne, verstreute Farbdeklarationen |
light-dark() in Custom Properties |
1 (zentral) | Am niedrigsten, ein Token pro Farbe | Design-Token-Systeme |
| Manueller JS-Umschalter über color-scheme | 0 zusätzlich | Unverändert zur gewählten Methode | Nutzergesteuerter Dark-Mode-Toggle |
Mironsoft
Modernes CSS, Layout-Architektur und Rendering-Performance
CSS, das wartbar bleibt statt mit jeder Änderung zu brechen?
Wir prüfen bestehende Stylesheets auf Spezifitäts-Chaos und Layout-Thrashing und bauen daraus eine CSS-Architektur mit Cascade Layers, Custom Properties und modernen Layout-Primitiven, die auch nach dem zehnten Feature noch verständlich ist.
CSS-Audit
Spezifität, Cascade-Konflikte und ungenutzte Selektoren systematisch aufdecken.
Architektur-Refactoring
Cascade Layers, Custom Properties und Design Tokens sauber einführen.
Performance-Tuning
Layout-Thrashing, teure Selektoren und Rendering-Engpässe gezielt beheben.
10. Zusammenfassung
light-dark() in CSS: Das Wichtigste auf einen Blick
Grundprinzip
light-dark(hell, dunkel) definiert ein Farbwertepaar in einer Deklaration, der Browser wählt anhand von color-scheme aus.
Voraussetzung
Ohne color-scheme: light dark; auf dem Root-Element bleibt light-dark() dauerhaft beim hellen Wert.
Vorteil ggü. Media Query
Beide Zustände stehen nebeneinander im Code, was das Vergessen eines Dunkelmodus-Werts unwahrscheinlicher macht.
Grenzen
Für strukturelle Unterschiede zwischen den Modi, nicht nur Farbe, bleibt ein klassischer prefers-color-scheme-Block nötig.