prefers-color-scheme im Detail: Dark Mode korrekt implementieren
AI generated
{ }
@
CSS · Dark Mode · Barrierefreiheit · Media Queries
prefers-color-scheme im Detail
Dark Mode korrekt implementieren, ohne Flash of wrong theme

prefers-color-scheme ist mehr als eine Media Query für Dunkel und Hell. Die color-scheme Property, ein sauberer Nutzer-Override und die richtige Reihenfolge beim ersten Render entscheiden darüber, ob Dark Mode nahtlos wirkt oder mit einem sichtbaren Aufblitzen des falschen Themes startet.

19 Min. Lesezeit prefers-color-scheme · color-scheme · localStorage · forced-colors Alle modernen Browser

1. Was prefers-color-scheme ist und woher der Wert kommt

prefers-color-scheme ist eine Media Query, die eine einzige Frage beantwortet: Bevorzugt das Betriebssystem des Nutzers ein helles oder ein dunkles Farbschema. Der Wert stammt nicht von der Webseite selbst, sondern wird global auf Betriebssystemebene eingestellt, etwa in den Systemeinstellungen von macOS, Windows, Android oder iOS, und gilt dort für alle Anwendungen gleichzeitig, die diese Einstellung respektieren.

Für Webseiten bedeutet das: Mit prefers-color-scheme lässt sich ein Dark Mode implementieren, der automatisch zur restlichen Betriebssystemumgebung des Nutzers passt, ohne dass ein eigener Toggle gebaut werden muss. Das ist der wichtigste Unterschied zu einem manuell implementierten Theme-Switch: Die Einstellung kommt von außen, aus einer bereits getroffenen Nutzerentscheidung, statt von einer neuen Entscheidung, die erst auf der jeweiligen Webseite getroffen werden muss.

2. Media Query Syntax: light, dark und die veraltete no-preference

Die Syntax von prefers-color-scheme ist unkompliziert: @media (prefers-color-scheme: dark) { ... } greift, wenn das Betriebssystem ein dunkles Schema bevorzugt, @media (prefers-color-scheme: light) { ... } greift beim hellen Schema. Ein dritter möglicher Wert, no-preference, wurde aus der Spezifikation entfernt, weil sich in der Praxis zeigte, dass Betriebssysteme fast immer eine explizite Präferenz liefern und der Wert kaum genutzt wurde.

Ein häufiger Anfängerfehler: Entwickler bauen zuerst ein komplettes helles Theme und ergänzen dann eine prefers-color-scheme: dark Media Query mit Überschreibungen für jede einzelne Farbe. Das führt zu doppeltem Pflegeaufwand und leicht auseinanderlaufenden Farbwerten. Der robustere Ansatz definiert Custom Properties als Zwischenschicht und überschreibt nur diese innerhalb der Media Query, statt jede einzelne CSS-Regel zu duplizieren.


/* Naive approach: duplicating rules per color scheme (avoid this) */
.card { background: white; color: #111827; }

@media (prefers-color-scheme: dark) {
  .card { background: #1e1b2e; color: #f8fafc; } /* duplicated selector */
}

/* Better: override custom properties, keep the rule itself unchanged */
:root {
  --surface: white;
  --on-surface: #111827;
}

@media (prefers-color-scheme: dark) {
  :root {
    --surface: #1e1b2e;
    --on-surface: #f8fafc;
  }
}

.card { background: var(--surface); color: var(--on-surface); }

3. Die color-scheme Property und native Formularelemente

Neben der Media Query gibt es die CSS-Property color-scheme, die dem Browser mitteilt, welche Farbschemata eine Seite unterstützt. Mit :root { color-scheme: light dark; } signalisiert eine Seite, dass sie sowohl helles als auch dunkles Rendering beherrscht. Der Browser passt daraufhin automatisch native UI-Elemente an, die nicht direkt per CSS gestylt werden, etwa Scrollbars, Formularfelder, Checkboxen und Datumsauswahl-Widgets.

Ohne diese Property kommt es häufig zu einem inkonsistenten Bild: Der selbst gestaltete Seiteninhalt folgt prefers-color-scheme korrekt und wirkt dunkel, während native Scrollbars und Formularelemente weiterhin hell und grell dazwischen erscheinen. Die color-scheme Property behebt genau dieses Problem, indem sie native Elemente in die Theme-Entscheidung einbezieht, ohne dass jedes einzelne Element manuell umgestaltet werden muss.


/* Tell the browser which color schemes the page supports */
:root {
  color-scheme: light dark;
}

/* Native form elements, scrollbars and date pickers now
   automatically follow prefers-color-scheme without extra CSS */
input, textarea, select {
  background: var(--surface);
  color: var(--on-surface);
}

4. Custom Properties automatisch je Farbschema umschalten

Die Kombination aus prefers-color-scheme und Custom Properties ist der Standardansatz für wartbare Themes. Statt zwei komplett getrennte Stylesheets zu pflegen, definiert man ein einziges Set semantischer Variablen, etwa --surface, --on-surface und --border, und überschreibt nur deren Werte innerhalb der prefers-color-scheme Media Query. Alle Komponenten referenzieren ausschließlich diese semantischen Variablen, nie direkte Farbwerte.

Dieser Ansatz skaliert gut, weil neue Komponenten automatisch themefähig sind, sobald sie die vorhandenen semantischen Variablen verwenden, ohne dass zusätzlicher Theme-Code geschrieben werden muss. Seit der neueren light-dark() Funktion lässt sich dieselbe Logik sogar noch kompakter direkt in der Variablendefinition ausdrücken, ohne die Media Query überhaupt explizit zu schreiben, wobei prefers-color-scheme und die color-scheme Property dafür weiterhin die Grundlage bilden.

5. Nutzer-Override trotz Betriebssystem-Einstellung

Viele Produkte wollen trotz prefers-color-scheme einen manuellen Theme-Toggle anbieten, damit Nutzer unabhängig von der Systemeinstellung wählen können. Das übliche Muster: Ein data-theme-Attribut am html-Element, das per JavaScript gesetzt und in localStorage gespeichert wird, überschreibt bei explizitem Nutzerwunsch die automatische prefers-color-scheme Erkennung. Fehlt das Attribut, greift weiterhin die Systemeinstellung als Standard.

Wichtig für ein sauberes Zusammenspiel: Die CSS-Selektoren für den manuellen Override müssen spezifischer sein als die reine Media Query, etwa [data-theme="dark"] als Attribut-Selektor, der die Media-Query-Regeln gezielt überschreibt. So bleibt prefers-color-scheme der sinnvolle Standardwert, während ein expliziter Nutzerwunsch stets Vorrang hat, ganz gleich, was das Betriebssystem meldet.


/* System preference as the default */
:root {
  --surface: white;
  --on-surface: #111827;
}

@media (prefers-color-scheme: dark) {
  :root { --surface: #1e1b2e; --on-surface: #f8fafc; }
}

/* Explicit user override always wins, regardless of the OS setting */
:root[data-theme="light"] { --surface: white; --on-surface: #111827; }
:root[data-theme="dark"]  { --surface: #1e1b2e; --on-surface: #f8fafc; }

// Persist and apply an explicit user override
function setTheme(theme) {
  document.documentElement.setAttribute('data-theme', theme);
  localStorage.setItem('theme', theme);
}

const saved = localStorage.getItem('theme');
if (saved) {
  document.documentElement.setAttribute('data-theme', saved);
}
// Without a saved value, prefers-color-scheme remains the source of truth

6. Testing in DevTools und Regressions-Erkennung

Chrome, Firefox und Safari erlauben im DevTools-Rendering-Tab, prefers-color-scheme unabhängig von der tatsächlichen Betriebssystemeinstellung zu emulieren. Das ist essenziell, weil ein Wechsel der echten Systemeinstellung während der Entwicklung umständlich ist und beide Zustände regelmäßig getestet werden müssen. Ein häufig übersehener Testfall: Elemente, die nur in einem der beiden Schemata sichtbar sind, etwa Icons mit fest codierter Farbe statt einer Custom Property, fallen bei diesem Test sofort auf.

Für automatisierte visuelle Regressionstests mit Playwright oder Puppeteer lässt sich prefers-color-scheme ebenfalls per Browser-Kontext-Option emulieren, sodass Screenshot-Vergleiche für beide Themes in der CI-Pipeline unabhängig voneinander laufen. Das deckt zuverlässig auf, wenn eine neue Komponente versehentlich eine hartkodierte Farbe statt einer themefähigen Variable verwendet.

7. Flash of wrong theme vermeiden bei SSR

Bei serverseitig gerenderten Seiten kann es zu einem kurzen Aufblitzen des falschen Themes kommen, wenn der Server das Theme nicht kennt und der Browser zunächst das Standard-Theme rendert, bevor JavaScript den gespeicherten Nutzer-Override anwendet. Dieses Problem ist unabhängig von prefers-color-scheme selbst, tritt aber häufig in Kombination mit einem Nutzer-Override auf, weil der Server den localStorage-Wert des Browsers nicht kennt.

Die gängige Lösung: Ein winziges Inline-Skript im <head>, das noch vor dem ersten Render der Seite ausgeführt wird und das data-theme-Attribut synchron setzt, bevor der Browser überhaupt mit dem Rendering beginnt. Da prefers-color-scheme selbst rein deklarativ und ohne JavaScript funktioniert, betrifft dieses Problem ausschließlich Seiten mit zusätzlichem manuellem Override, nicht den reinen automatischen Systemmodus.


<!-- Inline script in <head>, runs before first paint -->
<script>
  (function () {
    var saved = localStorage.getItem('theme');
    if (saved) {
      document.documentElement.setAttribute('data-theme', saved);
    }
  })();
</script>

8. Kombination mit prefers-contrast und forced-colors

prefers-color-scheme deckt nur die Achse Hell gegen Dunkel ab, nicht Kontrastanforderungen. Die verwandte Media Query prefers-contrast erkennt, ob der Nutzer erhöhten oder reduzierten Kontrast bevorzugt, und forced-colors erkennt den Windows High Contrast Mode, in dem der Browser eigene, vom System vorgegebene Farben erzwingt. Alle drei Media Queries lassen sich unabhängig voneinander kombinieren, weil sie unterschiedliche Nutzerpräferenzen abbilden.

Ein vollständig barrierefreies Farbsystem berücksichtigt prefers-color-scheme für die grundlegende Hell-Dunkel-Wahl, prefers-contrast für zusätzliche Kontraststufen innerhalb jedes Schemas, und respektiert forced-colors, indem es dort keine eigenen Hintergrundfarben erzwingt, sondern dem Betriebssystem die Kontrolle überlässt. Projekte, die nur prefers-color-scheme implementieren und die anderen beiden ignorieren, decken nur einen Teil der tatsächlichen Nutzerpräferenzen ab.

9. Implementierungsansätze im direkten Vergleich

Die folgende Tabelle vergleicht drei gängige Implementierungsansätze für Dark Mode.

Kriterium Nur Media Query Media Query + Override Nur JS-Toggle
Folgt Systemeinstellung Ja, automatisch Ja, als Standard Nein
Manuelle Nutzerwahl Nicht möglich Ja, mit Vorrang Ja
JavaScript nötig Nein Nur fürs Speichern Ja, zwingend
Flash-Risiko bei SSR Keines Vermeidbar mit Inline-Skript Hoch ohne Inline-Skript
Wartungsaufwand Gering Mittel Hoch

Für die meisten Projekte ist die Kombination aus prefers-color-scheme als Standardwert und einem optionalen, per Attribut überschreibenden Nutzer-Toggle der beste Kompromiss. Reine JS-Toggles ohne Berücksichtigung der Systemeinstellung ignorieren eine bereits getroffene Nutzerentscheidung und sollten nur in Ausnahmefällen die alleinige Lösung sein.

Mironsoft

Dark Mode Implementierung, Theme-Architektur und Barrierefreiheit

Dark Mode ohne Flash of wrong theme?

Wir implementieren prefers-color-scheme mit sauberem Nutzer-Override, ohne sichtbares Aufblitzen beim ersten Laden, und stellen sicher, dass native Formularelemente, Kontrast-Präferenzen und Windows High Contrast Mode korrekt zusammenspielen.

Theme-Architektur

Semantische Custom Properties statt doppelter Farbregeln pro Schema

SSR-Fix

Inline-Skript gegen Flash of wrong theme bei serverseitigem Rendering

Barrierefreiheit

Kombination mit prefers-contrast und forced-colors korrekt umgesetzt

10. Zusammenfassung

prefers-color-scheme ist der Ausgangspunkt für jeden modernen Dark Mode, aber erst im Zusammenspiel mit weiteren Techniken entsteht ein wirklich sauberes Ergebnis. Die color-scheme Property bindet native UI-Elemente in die Theme-Entscheidung ein, semantische Custom Properties reduzieren Wartungsaufwand, und ein Attribut-basierter Nutzer-Override erlaubt eine bewusste Abweichung von der Systemeinstellung, ohne die Media Query als sinnvollen Standard zu verlieren.

Bei serverseitig gerenderten Anwendungen verhindert ein winziges Inline-Skript im Head das Aufblitzen des falschen Themes, während die Kombination mit prefers-contrast und forced-colors dafür sorgt, dass auch Nutzer mit besonderen Kontrastanforderungen vollständig berücksichtigt werden. Wer alle diese Bausteine zusammenführt, erhält ein Dark Mode System, das sich für Nutzer wie eine native, nahtlose Betriebssystemfunktion anfühlt.

prefers-color-scheme im Detail — Das Wichtigste auf einen Blick

Grundmuster

@media (prefers-color-scheme: dark) überschreibt Custom Properties, nie einzelne CSS-Regeln direkt duplizieren.

color-scheme Property

color-scheme: light dark; passt native Scrollbars und Formularelemente automatisch an.

Flash of wrong theme

Inline-Skript im Head setzt das Attribut synchron, noch vor dem ersten Render der Seite.

Vollständige Barrierefreiheit

prefers-contrast und forced-colors zusätzlich zu prefers-color-scheme berücksichtigen.

11. FAQ: prefers-color-scheme im Detail

1Was ist prefers-color-scheme?
Eine Media Query, die die Hell/Dunkel-Präferenz des Betriebssystems abfragt, gesetzt in den Systemeinstellungen.
2Wozu die color-scheme Property?
Passt native Elemente wie Scrollbars und Formularfelder automatisch an prefers-color-scheme an.
3Wie baue ich einen Toggle?
data-theme Attribut per JavaScript setzen, in localStorage speichern, Selektor spezifischer als die Media Query.
4Was ist Flash of wrong theme?
Kurzes Aufblitzen des falschen Themes bei SSR, vermeidbar mit Inline-Skript im Head vor dem Rendering.
5Unterschied zu prefers-contrast?
color-scheme betrifft Hell/Dunkel, prefers-contrast betrifft Kontraststufen. Beide sollten kombiniert werden.
6Wie teste ich ohne Systemänderung?
Im DevTools-Rendering-Tab lässt sich die Media Query unabhängig von der echten Einstellung emulieren.
7Regeln pro Schema duplizieren?
Nein, Custom Properties als Zwischenschicht nutzen und nur diese in der Media Query überschreiben.
8Was ist forced-colors?
Erkennt Windows High Contrast Mode, eigene Farben dort zurücknehmen und dem System die Kontrolle überlassen.
9Gibt es no-preference noch?
Nein, entfernt aus der Spezifikation, da Systeme fast immer eine explizite Präferenz liefern.
10Reicht ein reiner JS-Toggle?
Nicht empfohlen, ignoriert die Systemeinstellung. Besser prefers-color-scheme als Standard mit Override.