Leitfaden für den Dark Mode in CSS

Avatar of Adhuham
Adhuham am

DigitalOcean bietet Cloud-Produkte für jede Phase Ihrer Reise. Starten Sie mit 200 $ kostenlosem Guthaben!

Der Dark Mode hat in letzter Zeit stark an Bedeutung gewonnen. Apple hat beispielsweise den Dark Mode in seine Betriebssysteme iOS und MacOS integriert. Windows und Google haben dasselbe getan. 

Die hellen und dunklen Themes von DuckDuckGo

Befassen wir uns mit dem Dark Mode im Kontext von Websites. Wir werden verschiedene Optionen und Ansätze zur Implementierung eines Dark-Mode-Designs sowie die damit verbundenen technischen Überlegungen vertiefen. Zudem werden wir zwischendurch einige Design-Tipps ansprechen.


Themes umschalten

Das typische Szenario ist, dass Sie bereits ein helles Theme für Ihre Website haben und nun ein dunkles Gegenstück erstellen möchten. Oder, selbst wenn Sie bei Null anfangen, werden Sie beide Themes haben: hell und dunkel. Ein Theme sollte als Standard definiert sein, den Benutzer beim ersten Besuch sehen – in den meisten Fällen das helle Theme (obwohl wir den Browser des Benutzers diese Wahl für uns treffen lassen können, wie wir noch sehen werden). Es sollte auch eine Möglichkeit geben, zum anderen Theme zu wechseln (was ebenfalls automatisch geschehen kann) – zum Beispiel, indem der Benutzer auf eine Schaltfläche klickt und sich das Farbschema ändert.

Es gibt verschiedene Ansätze, um dies umzusetzen

Verwendung einer Body-Klasse

Der Trick hierbei ist der Austausch einer Klasse, die als Ankerpunkt dient, um den Stil an jeder Stelle der Seite zu ändern.

<body class="dark-theme || light-theme">

Hier ist zum Beispiel ein Skript für eine Schaltfläche, die diese Klasse umschaltet

// Select the button
const btn = document.querySelector('.btn-toggle');

// Listen for a click on the button
btn.addEventListener('click', function() {
  // Then toggle (add/remove) the .dark-theme class to the body
  document.body.classList.toggle('dark-theme');  
})

So können wir diese Idee nutzen

<body>
  <button class="btn-toggle">Toggle Dark Mode</button>
  <h1>Hey there! This is just a title</h1>
  <p>I am just a boring text, existing here solely for the purpose of this demo</p>
  <p>And I am just another one like the one above me, because two is better than having only one</p>
  <a href="#">I am a link, don't click me!</a>
</body>

Die allgemeine Idee dieses Ansatzes ist es, die Dinge wie gewohnt zu stylen, dies als unseren „Standard“-Modus zu bezeichnen und dann einen vollständigen Satz an Farbstilen unter Verwendung einer Klasse am <body>-Element zu erstellen, die wir als „Dark“-Modus nutzen.

Nehmen wir an, unser Standard ist ein helles Farbschema. All diese „hellen“ Stile werden genau so geschrieben, wie Sie normalerweise CSS schreiben. Wenden wir auf Basis unseres HTML ein globales Styling auf den Body und auf Links an.

body {
  color: #222;
  background: #fff;
}
a {
  color: #0033cc;
}

Gut. Wir haben dunklen Text (#222) und dunkle Links (#0033cc) auf einem hellen Hintergrund (#fff). Unser „Standard“-Theme hat einen soliden Start hingelegt.

Nun definieren wir diese Eigenschaftswerte neu, diesmal für eine andere Body-Klasse

body {
  color: #222;
  background: #fff;
}
a {
  color: #0033cc;
}


/* Dark Mode styles */
body.dark-theme {
  color: #eee;
  background: #121212;
}
body.dark-theme a {
  color: #809fff;
}

Die Stile für das dunkle Theme sind Nachfahren derselben Elternklasse – in diesem Beispiel .dark-theme –, die wir auf den <body>-Tag angewendet haben.

Wie „wechseln“ wir die Body-Klassen, um auf die dunklen Stile zuzugreifen? Wir können JavaScript verwenden! Wir wählen die Schaltflächen-Klasse (.btn-toggle) aus, fügen einen Listener für den Klick hinzu und hängen dann die Dark-Theme-Klasse (.dark-theme) an die Klassenliste des Body-Elements an. Dank Kaskade und Spezifität werden dadurch effektiv alle gesetzten „hellen“ Farben überschrieben. 

Hier ist der vollständige Code in Aktion. Klicken Sie auf die Umschalttaste, um den Dark Mode ein- und auszuschalten.

Verwendung separater Stylesheets

Anstatt alle Stile in einem Stylesheet zusammenzufassen, könnten wir zwischen Stylesheets für jedes Theme umschalten. Dies setzt voraus, dass Sie vollständige Stylesheets bereitliegen haben.

Zum Beispiel ein Standard-Hell-Theme wie light-theme.css

/* light-theme.css */


body {
  color: #222;
  background: #fff;
}
a {
  color: #0033cc;
}

Dann erstellen wir Stile für das dunkle Theme und speichern sie in einem separaten Stylesheet namens dark-theme.css.

/* dark-theme.css */


body {
  color: #eee;
  background: #121212;
}
body a {
  color: #809fff;
}

Dies gibt uns zwei separate Stylesheets – eines für jedes Theme –, die wir im HTML-Bereich <head> verknüpfen können. Verknüpfen wir zuerst die hellen Stile, da wir diese als Standard bezeichnen.

<!DOCTYPE html>
<html lang="en">
<head>
  <!-- Light theme stylesheet -->
  <link href="light-theme.css" rel="stylesheet" id="theme-link">
</head>


<!-- etc. -->


</html>

Wir verwenden eine ID #theme-link, die wir mit JavaScript auswählen können, um erneut zwischen hellem und dunklem Modus zu wechseln. Nur dass wir dieses Mal Dateien anstatt Klassen umschalten.

// Select the button
const btn = document.querySelector(".btn-toggle");
// Select the stylesheet <link>
const theme = document.querySelector("#theme-link");

// Listen for a click on the button
btn.addEventListener("click", function() {
  // If the current URL contains "ligh-theme.css"
  if (theme.getAttribute("href") == "light-theme.css") {
    // ... then switch it to "dark-theme.css"
    theme.href = "dark-theme.css";
  // Otherwise...
  } else {
    // ... switch it to "light-theme.css"
    theme.href = "light-theme.css";
  }
});

Verwendung von Custom Properties (CSS-Variablen)

Wir können auch die Leistungsfähigkeit von CSS Custom Properties nutzen, um ein dunkles Theme zu erstellen! Dies hilft uns, separate Regelsätze für jedes Theme zu vermeiden, wodurch das Schreiben von Stilen viel schneller und Änderungen an einem Theme viel einfacher werden.

Wir können uns immer noch dafür entscheiden, eine Body-Klasse zu tauschen und diese Klasse zu verwenden, um die Custom Properties neu zu setzen.

// Select the button
const btn = document.querySelector(".btn-toggle");


// Listen for a click on the button
btn.addEventListener("click", function() {
  // Then toggle (add/remove) the .dark-theme class to the body
  document.body.classList.toggle("dark-theme");
});

Zuerst definieren wir die Standardwerte für helle Farben als Custom Properties im Body-Element

body {
  --text-color: #222;
  --bkg-color: #fff;
  --anchor-color: #0033cc;
}

Nun können wir diese Werte für eine Body-Klasse .dark-theme neu definieren, genau wie wir es in der ersten Methode getan haben

body.dark-theme {
  --text-color: #eee;
  --bkg-color: #121212;
  --anchor-color: #809fff;
}

Hier sind unsere Regelsätze für die Body- und Link-Elemente unter Verwendung von Custom Properties

body {
  color: var(--text-color);
  background: var(--bkg-color);
}
a {
  color: var(--anchor-color);
}

Wir hätten unsere Custom Properties ebenso gut im Dokument-:root definieren können. Das ist völlig legitim und sogar gängige Praxis. In diesem Fall kämen alle Definitionen des Standard-Themes in :root { } und alle Dark-Theme-Eigenschaften in :root.dark-mode { }.

Verwendung von serverseitigen Skripten

Wenn wir bereits mit einer serverseitigen Sprache arbeiten, zum Beispiel PHP, dann können wir diese anstelle von JavaScript verwenden. Dies ist ein großartiger Ansatz, wenn Sie es vorziehen, direkt im Markup zu arbeiten.

<?php
$themeClass = '';
if (isset($_GET['theme']) && $_GET['theme'] == 'dark') {
  $themeClass = 'dark-theme';
}


$themeToggle = ($themeClass == 'dark-theme') ? 'light' : 'dark';
?>
<!DOCTYPE html>
<html lang="en">
<!-- etc. -->
<body class="<?php echo $themeClass; ?>">
  <a href="?theme=<?php echo $themeToggle; ?>">Toggle Dark Mode</a>
  <!-- etc. -->
</body>
</html>

Wir können den Benutzer eine GET- oder POST-Anfrage senden lassen. Dann lassen wir unseren Code (in diesem Fall PHP) die entsprechende Body-Klasse anwenden, wenn die Seite neu geladen wird. Für diese Demonstration verwende ich eine GET-Anfrage (URL-Parameter).

Und ja, wir können Stylesheets genauso austauschen, wie wir es in der zweiten Methode getan haben.

<?php
$themeStyleSheet = 'light-theme.css';
if (isset($_GET['theme']) && $_GET['theme'] == 'dark') {
  $themeStyleSheet = 'dark-theme.css';
}


$themeToggle = ($themeStyleSheet == 'dark-theme.css') ? 'light' : 'dark';
?>
<!DOCTYPE html>
<html lang="en">
<head>
  <!-- etc. -->
  <link href="<?php echo $themeStyleSheet; ?>" rel="stylesheet">
</head>


<body>
  <a href="?theme=<?php echo $themeToggle; ?>">Toggle Dark Mode</a>
  <!-- etc. -->
</body>
</html>

Diese Methode hat einen offensichtlichen Nachteil: Die Seite muss aktualisiert werden, damit die Umschaltung erfolgt. Aber eine serverseitige Lösung wie diese ist nützlich, um die Theme-Wahl des Benutzers über Seitenaktualisierungen hinweg beizubehalten, wie wir später sehen werden.


Welche Methode sollten Sie wählen?

Die „richtige“ Methode hängt von den Anforderungen Ihres Projekts ab. Bei einem großen Projekt könnten Sie beispielsweise auf CSS-Variablen setzen, um eine umfangreiche Codebasis besser zu verwalten. Wenn Ihr Projekt hingegen ältere Browser unterstützen muss, ist ein anderer Ansatz erforderlich.

Zudem spricht nichts dagegen, mehrere Methoden zu kombinieren. Manchmal ist eine Kombination von Ansätzen der effektivste Weg. Es mag sogar noch andere mögliche Methoden geben, als die hier besprochenen.


Dark Mode auf Betriebssystemebene

Bisher haben wir eine Schaltfläche zum Umschalten zwischen hellem und dunklem Modus verwendet, aber wir können diese Arbeit auch einfach dem Betriebssystem des Benutzers überlassen. Beispielsweise erlauben es viele Betriebssysteme den Benutzern, direkt in den Systemeinstellungen zwischen hellen und dunklen Themes zu wählen.

Die „Allgemein“-Einstellungen in den MacOS-Systemeinstellungen

Reines CSS

Details

Glücklicherweise verfügt CSS über eine Media Query namens prefers-color-scheme, mit der die Farbschema-Präferenzen des Benutzers erkannt werden können. Sie kann drei mögliche Werte haben: keine Präferenz, hell und dunkel. Lesen Sie mehr darüber auf MDN.

@media (prefers-color-scheme: dark) {
  /* Dark theme styles go here */
}


@media (prefers-color-scheme: light) {
  /* Light theme styles go here */
}

Um sie zu nutzen, können wir die Stile für das dunkle Theme in die Media Query einfügen.

@media (prefers-color-scheme: dark) {
  body {
    color: #eee;
    background: #121212;
  }


  a {
    color: #809fff;
  }
}

Wenn ein Benutzer nun den Dark Mode in den Systemeinstellungen aktiviert hat, erhält er standardmäßig die Dark-Mode-Stile. Wir müssen nicht auf JavaScript oder serverseitige Skripte zurückgreifen, um zu entscheiden, welcher Modus verwendet werden soll. Wir brauchen nicht einmal mehr die Schaltfläche!

JavaScript

Details

Wir können JavaScript nutzen, um das bevorzugte Farbschema des Benutzers zu erkennen. Dies ähnelt stark der ersten Methode, nur dass wir matchMedia() verwenden, um die Präferenz des Benutzers abzufragen.

const prefersDarkScheme = window.matchMedia('(prefers-color-scheme: dark)');nnif (prefersDarkScheme.matches) {n  document.body.classList.add('dark-theme');n} else {n  document.body.classList.remove('dark-theme');n}

Die Verwendung von JavaScript hat einen Nachteil: Da JavaScript erst nach dem CSS ausgeführt wird, kann es zu einem kurzen Aufblitzen des hellen Themes kommen. Das könnte als Fehler missverstanden werden.

Natürlich können wir stattdessen auch Stylesheets tauschen, wie in der zweiten Methode. Diesmal verknüpfen wir beide Stylesheets und nutzen die Media Query, um zu bestimmen, welches angewendet wird.

OS-Einstellungen überschreiben

Wir haben uns gerade angesehen, wie man die systemweiten Farbschema-Präferenzen eines Benutzers berücksichtigt. Aber was ist, wenn Benutzer ihre Systemeinstellung für eine bestimmte Seite überschreiben wollen? Nur weil ein Benutzer den Dark Mode für sein Betriebssystem bevorzugt, heißt das nicht unbedingt, dass er ihn auch auf einer Website möchte. Daher ist es eine gute Idee, eine Möglichkeit zu bieten, den Dark Mode manuell zu überschreiben, ungeachtet der Systemeinstellungen.

Code ansehen

Nutzen wir den Ansatz mit CSS-Variablen, um zu zeigen, wie das geht. Die Idee ist, die Variablen für beide Themes wie zuvor zu definieren, die Dark-Styles in die prefers-color-scheme Media Query zu packen und darin eine .light-theme-Klasse zu definieren, mit der wir die Dark-Mode-Eigenschaften überschreiben können, falls der Benutzer zwischen den beiden Modi wechseln möchte.

/* Default colors */
body {
  --text-color: #222;
  --bkg-color: #fff;
}
/* Dark theme colors */
body.dark-theme {
  --text-color: #eee;
  --bkg-color: #121212;
}

/* Styles for users who prefer dark mode at the OS level */
@media (prefers-color-scheme: dark) {
  /* defaults to dark theme */
  body { 
    --text-color: #eee;
    --bkg-color: #121212;
  }
  /* Override dark mode with light mode styles if the user decides to swap */
  body.light-theme {
    --text-color: #222;
    --bkg-color: #fff;
  }
}

Nun können wir wieder auf unsere bewährte Schaltfläche zurückgreifen, um zwischen hellen und dunklen Themes umschalten zu können. Auf diese Weise respektieren wir standardmäßig die Farbschema-Präferenz des Betriebssystems und ermöglichen dem Benutzer, manuell zu wechseln.

// Listen for a click on the button 
btn.addEventListener("click", function() {
  // If the OS is set to dark mode...
  if (prefersDarkScheme.matches) {
    // ...then apply the .light-theme class to override those styles
    document.body.classList.toggle("light-theme");
    // Otherwise...
  } else {
    // ...apply the .dark-theme class to override the default light styles
    document.body.classList.toggle("dark-theme");
  }
});

Browser-Unterstützung

Die Media Query prefers-color-scheme wird von den wichtigsten Browsern unterstützt, darunter Chrome 76+, Firefox 67+, Chrome Android 76+, Safari 12.5+ (13+ auf iOS) und Samsung Internet Browser. Der IE wird nicht unterstützt.

Das ist eine vielversprechende Unterstützung! Can I Use schätzt die Benutzerabdeckung auf 80,85 %.

Zu den Betriebssystemen, die derzeit den Dark Mode unterstützen, gehören MacOS (Mojave oder neuer), iOS (13.0+), Windows (10+) und Android (10+).


Speichern der Benutzereinstellungen

Was wir uns bisher angesehen haben, tut genau das, was es verspricht: den Theme-Wechsel basierend auf einer OS-Präferenz oder einem Klick. Das ist toll, bleibt aber nicht erhalten, wenn der Benutzer eine andere Seite der Website besucht oder die aktuelle Seite neu lädt.

Wir müssen die Wahl des Benutzers speichern, damit sie auf der gesamten Website und bei künftigen Besuchen konsistent angewendet wird. Dazu können wir die Wahl des Benutzers beim Umschalten im localStorage speichern. Cookies sind für diese Aufgabe ebenfalls gut geeignet.

Sehen wir uns beide Ansätze an.

Verwendung von localStorage

Wir haben ein Skript, das das ausgewählte Theme beim Umschalten im localStorage speichert. Wenn die Seite neu geladen wird, ruft das Skript die Wahl aus dem localStorage ab und wendet sie an. Da JavaScript oft erst nach dem CSS ausgeführt wird, ist dieser Ansatz anfällig für ein „kurzes Aufblitzen des falschen Themes“ (Flash of Incorrect Theme, FOIT).

Code ansehen
// Select the button
const btn = document.querySelector(".btn-toggle");
// Select the theme preference from localStorage
const currentTheme = localStorage.getItem("theme");


// If the current theme in localStorage is "dark"...
if (currentTheme == "dark") {
  // ...then use the .dark-theme class
  document.body.classList.add("dark-theme");
}


// Listen for a click on the button 
btn.addEventListener("click", function() {
  // Toggle the .dark-theme class on each click
  document.body.classList.toggle("dark-theme");
  
  // Let's say the theme is equal to light
  let theme = "light";
  // If the body contains the .dark-theme class...
  if (document.body.classList.contains("dark-theme")) {
    // ...then let's make the theme dark
    theme = "dark";
  }
  // Then save the choice in localStorage
  localStorage.setItem("theme", theme);
});

Verwendung von Cookies mit PHP

Um das Flashen zu vermeiden, können wir ein serverseitiges Skript wie PHP verwenden. Anstatt die Theme-Präferenz des Benutzers im localStorage zu speichern, erstellen wir ein Cookie via JavaScript. Aber auch das ist nur dann sinnvoll, wenn Sie bereits mit einer serverseitigen Sprache arbeiten.

Code ansehen
// Select the button
const btn = document.querySelector(".btn-toggle");


// Listen for a click on the button 
btn.addEventListener("click", function() {
  // Toggle the .dark-theme class on the body
  document.body.classList.toggle("dark-theme");
  
  // Let's say the theme is equal to light
  let theme = "light";
  // If the body contains the .dark-theme class...
  if (document.body.classList.contains("dark-theme")) {
    // ...then let's make the theme dark
    theme = "dark";
  }
  // Then save the choice in a cookie
  document.cookie = "theme=" + theme;
});

Wir können nun prüfen, ob dieses Cookie existiert, und das entsprechende Theme laden, indem wir die richtige Klasse auf den <body>-Tag anwenden.

<?php
$themeClass = '';
if (!empty($_COOKIE['theme']) && $_COOKIE['theme'] == 'dark') {
  $themeClass = 'dark-theme';
}
?>


<!DOCTYPE html>
<html lang="en">
<!-- etc. -->
<body class="<?php echo $themeClass; ?>">
<!-- etc. -->
</body>
</html>

So geht das mit der Methode der separaten Stylesheets

<?php
$themeStyleSheet = 'light-theme.css';
if (!empty($_COOKIE['theme']) && $_COOKIE['theme'] == 'dark') {
  $themeStyleSheet = 'dark-theme.css';
}
?>


<!DOCTYPE html>
<html lang="en">
<head>
  <!-- etc. -->
  <link href="<?php echo $themeStyleSheet; ?>" rel="stylesheet" id="theme-link">
</head>
<!-- etc. -->

Wenn Ihre Website über Benutzerkonten verfügt – also einen Bereich zum Einloggen und Verwalten von Profildaten –, ist dies ebenfalls ein hervorragender Ort, um Theme-Präferenzen zu speichern. Senden Sie diese an die Datenbank, in der die Kontodaten gespeichert sind. Wenn sich der Benutzer anmeldet, rufen Sie das Theme aus der Datenbank ab und wenden es mittels PHP (oder einem anderen serverseitigen Skript) auf die Seite an.

Es gibt verschiedene Wege, dies zu tun. In diesem Beispiel rufe ich die Theme-Präferenz des Benutzers aus der Datenbank ab und speichere sie zum Zeitpunkt des Logins in einer Session-Variablen.

<?php
// Login action
if (!empty($_POST['login'])) {
  // etc.


  // If the uuser is authenticated...
  if ($loginSuccess) {
    // ... save their theme preference to a session variable
    $_SESSION['user_theme'] = $userData['theme'];
  }
}


// Pick the session variable first if it's set; otherwise pick the cookie
$themeChoice = $_SESSION['user_theme'] ?? $_COOKIE['theme'] ?? null;
$themeClass = '';
if ($themeChoice == 'dark') {
  $themeClass = 'dark-theme';
}
?>


<!DOCTYPE html>
<html lang="en">
<!-- etc. -->
<body class="<?php echo $themeClass; ?>">
<!-- etc. -->
</body>
</html>

Ich verwende den Null-Coalescing-Operator von PHP (??), um zu entscheiden, woher die Theme-Präferenz bezogen werden soll: aus der Session oder aus dem Cookie. Wenn der Benutzer eingeloggt ist, wird der Wert der Session-Variablen anstelle des Cookies genommen. Und wenn der Benutzer nicht eingeloggt ist oder sich ausgeloggt hat, wird der Wert des Cookies verwendet.


Umgang mit User-Agent-Styles

Um das UA-Stylesheet des Browsers über die systemweiten Farbschema-Präferenzen zu informieren und ihm mitzuteilen, welche Farbschemata auf der Seite unterstützt werden, können wir das Meta-Tag color-scheme verwenden.

Nehmen wir zum Beispiel an, die Seite soll sowohl „dark“ als auch „light“ Themes unterstützen. Wir können beide als Werte im Meta-Tag angeben, getrennt durch Leerzeichen. Wenn wir nur ein „helles“ Theme unterstützen wollen, müssen wir nur „light“ als Wert verwenden. Dies wird in einem CSSWG-GitHub-Issue diskutiert, in dem es ursprünglich vorgeschlagen wurde.

<meta name="color-scheme" content="dark light">

Wenn dieses Meta-Tag hinzugefügt wird, berücksichtigt der Browser die Farbschema-Präferenzen des Benutzers beim Rendern von UA-gesteuerten Elementen der Seite (wie etwa einem <button>). Er rendert Farben für den Hintergrund des Root-Elements, Formularsteuerelemente und Rechtschreibprüfungsfunktionen (sowie alle anderen UA-gesteuerten Stile) basierend auf der Präferenz des Benutzers.

Quelle

Obwohl Themes größtenteils manuell gestaltet werden (was die UA-Styles überschreibt), hilft es, den Browser über die unterstützten Themes zu informieren, um selbst die geringste Chance auf eine potenzielle FOIT-Situation zu vermeiden. Dies gilt für jene Fälle, in denen das HTML bereits gerendert wurde, das CSS aber noch geladen wird.

Wir können dies auch in CSS festlegen

:root {
  color-scheme: light dark; /* both supported */
}
via Jim Nielsen

Zum Zeitpunkt der Erstellung dieses Artikels mangelt es der Eigenschaft color-scheme noch an breiter Browserunterstützung, obwohl sowohl Safari als auch Chrome sie unterstützen.


Alles kombinieren!

Kombinieren wir alles und erstellen eine funktionierende Demo, die

  1. automatisch ein dunkles oder helles Theme basierend auf den Systemeinstellungen lädt
  2. es dem Benutzer ermöglicht, seine Systemeinstellung manuell zu überschreiben
  3. das bevorzugte Theme des Benutzers bei Seitenaktualisierungen beibehält

Verwendung von JavaScript & Local Storage

// Select the button
const btn = document.querySelector(".btn-toggle");
// Check for dark mode preference at the OS level
const prefersDarkScheme = window.matchMedia("(prefers-color-scheme: dark)");


// Get the user's theme preference from local storage, if it's available
const currentTheme = localStorage.getItem("theme");
// If the user's preference in localStorage is dark...
if (currentTheme == "dark") {
  // ...let's toggle the .dark-theme class on the body
  document.body.classList.toggle("dark-mode");
// Otherwise, if the user's preference in localStorage is light...
} else if (currentTheme == "light") {
  // ...let's toggle the .light-theme class on the body
  document.body.classList.toggle("light-mode");
}


// Listen for a click on the button 
btn.addEventListener("click", function() {
  // If the user's OS setting is dark and matches our .dark-mode class...
  if (prefersDarkScheme.matches) {
    // ...then toggle the light mode class
    document.body.classList.toggle("light-mode");
    // ...but use .dark-mode if the .light-mode class is already on the body,
    var theme = document.body.classList.contains("light-mode") ? "light" : "dark";
  } else {
    // Otherwise, let's do the same thing, but for .dark-mode
    document.body.classList.toggle("dark-mode");
    var theme = document.body.classList.contains("dark-mode") ? "dark" : "light";
  }
  // Finally, let's save the current preference to localStorage to keep using it
  localStorage.setItem("theme", theme);
});

Verwendung von PHP & Cookies

<?php
$themeClass = '';
if (!empty($_COOKIE['theme'])) {
  if ($_COOKIE['theme'] == 'dark') {
    $themeClass = 'dark-theme';
  } else if ($_COOKIE['theme'] == 'light') {
    $themeClass = 'light-theme';
  }  
}
?>


<!DOCTYPE html>
<html lang="en">
<!-- etc. -->
<body class="<?php echo $themeClass; ?>">
<!-- etc. -->
<script>
  const btn = document.querySelector(".btn-toggle");
  const prefersDarkScheme = window.matchMedia("(prefers-color-scheme: dark)");
  
  btn.addEventListener("click", function() {
    if (prefersDarkScheme.matches) {
      document.body.classList.toggle("light-mode");
      var theme = document.body.classList.contains("light-mode") ? "light" : "dark";
    } else {
      document.body.classList.toggle("dark-mode");
      var theme = document.body.classList.contains("dark-mode") ? "dark" : "light";
    }
    document.cookie = "theme=" + theme;
  });
</script>
</body>
</html>

Design-Überlegungen

Ich höre oft, dass die Implementierung des Dark Mode einfacher sei als dessen Gestaltung. Ohne das bewerten zu wollen, werfen wir einen Blick auf einige Überlegungen zur Gestaltung eines dunklen Themes.

Sie kennen bereits die grundlegende Aufgabe: helle Farbwerte durch dunklere ersetzen und umgekehrt. Aber es gibt einige UI-Elemente und Verfeinerungen, die nuancierter sind und mehr Aufmerksamkeit erfordern. Sehen wir uns diese an.

Bilder im Dark Mode

Eine gute Regel ist es, Helligkeit und Kontrast von Bildern etwas zu verringern, damit sie vor einem dunklen Hintergrund angenehm für die Augen wirken. Ein extrem helles Bild auf einem sehr dunklen Hintergrund kann störend wirken; das Abdunkeln des Bildes reduziert diesen starken Kontrast.

Die CSS-Funktion filter() ist bestens geeignet, dies für uns zu erledigen

/* Apply the filter directly on the body tag */
body.dark-theme img {
  filter: brightness(.8) contrast(1.2);
}


/* Or apply it via media query */
@media (prefers-color-scheme: dark) {
  img {
    filter: brightness(.8) contrast(1.2);
  }
}

Dasselbe können wir direkt im Markup tun, indem wir das <picture>-Element verwenden, um verschiedene Versionen eines Bildes zu laden

<picture>
  <!-- Use this image if the user's OS setting is light or unset -->
  <source srcset="photo-light.png" media="(prefers-color-scheme: light) or (prefers-color-scheme: no-preference)">
  <!-- Use this image if the user's OS setting is dark -->
  <source srcset="photo-dark.png" media="(prefers-color-scheme: dark)">
</picture>

Der Nachteil hierbei ist, dass zwei Dateien bereitgestellt werden müssen, während wir bei der CSS-Lösung nur mit einer arbeiten. Zudem wird hierbei nicht vollständig berücksichtigt, wenn der Benutzer das Farbschema auf der Seite manuell umschaltet.

Schatten im Dark Mode

Schatten im Dark Mode sind knifflig. Wenn wir einfach einen dunklen Schatten durch helle Farben invertieren, erhalten wir einen seltsamen Effekt mit einem hellen Schatten auf dunklem Hintergrund… und das sieht nicht gut aus.

Es ist möglich, einen dunklen Schatten im Dark Mode zu verwenden, aber die Hintergrundfarbe muss „hell“ genug sein (wie ein dunkles Grau), um genügend Kontrast zu bieten, damit man den Schatten überhaupt wahrnimmt.

Verwenden Sie Opazität, um Tiefe zu vermitteln, wobei Regionen mit hoher Opazität eine geringere Tiefe haben. Das heißt, Elemente mit einer höheren Erhebung sollten eine geringere Opazität haben als Elemente, die in der Tiefe „näher“ am Hintergrund liegen.

Verschiedene Farbschattierungen erzeugen unterschiedliche Wahrnehmungen von „Tiefe“

Typografie im Dark Mode

Der Trick hier ist ähnlich wie bei Bildern: Wir müssen den Kontrast ausbalancieren. Eine zu fette Schrift führt zu grellem Text, der einen vom Bildschirm weichen lässt. Eine zu feine Schrift lässt uns die Augen anstrengen, während wir immer näher an den Bildschirm rücken.

Das Gleichgewicht liegt irgendwo in der Mitte. Robin hat einen schönen Artikel geschrieben, in dem er ein kleines Stück CSS vorschlägt, das einen großen Unterschied in der Lesbarkeit macht.

Icons im Dark Mode

Icons fallen in diese „knifflige“ Kategorie, da sie eine Mischung aus Text und Bild sind. Wenn wir mit SVG-Icons arbeiten, können wir jedoch die Füllung (fill) per CSS ändern. Wenn wir hingegen Icon-Fonts verwenden, können wir einfach die Farbgigenschaft (color) ändern.

/* SVG icon */
body.dark-theme svg.icon path {
  fill: #efefef;
}
/* Font icon (using Font Awesome as an example) */
body.dark-theme .fa {
  color: #efefef;
}

Viele der Design-Überlegungen, die für Text gelten, lassen sich generell auch auf Icons übertragen. Beispielsweise sollten wir reines Weiß und sehr dicke Umrisse vermeiden.

Farben im Dark Mode

Rein weißer Text auf rein schwarzem Hintergrund wirkt störend. Der Trick besteht darin, ein Off-White für den Text und ein Off-Black für den Hintergrund zu verwenden. Die Material Design Richtlinien empfehlen beispielsweise #121212 für den Hintergrund.

Farbpaletten im Dark Mode

Wir haben gesehen, welchen Unterschied die Verwendung von Off-White- und Off-Black-Farben für Text und Bilder macht. Bauen wir das mit Tipps zur Entwicklung einer vollständigen Farbpalette etwas aus.

Vieles lässt sich auf eines reduzieren: Kontrast. Deshalb ist der erste Tipp, bevor man sich auf eine Farbe festlegt, Ideen durch einen Kontrast-Checker laufen zu lassen. So wird sichergestellt, dass die Farbhältnisse den WCAG-Richtlinien für mindestens eine AA-Bewertung entsprechen, was einem Kontrastverhältnis von 4,5:1 entspricht.

Das bedeutet, dass entsättigte Farben unsere Freunde sind, wenn wir an einem Dark-Mode-Design arbeiten. Sie helfen, übermäßig grelle Bilder zu vermeiden und geben uns dennoch genügend Spielraum, um ein effektives Kontrastverhältnis zu schaffen.

Denken Sie als Nächstes daran, dass Akzentfarben zur Verfeinerung dienen. Sie sind wahrscheinlich heller als die Hintergrundfarbe des dunklen Themes. Wenn man sie also wie eine Primärfarbe oder als Hintergrundfarbe eines großen Containers verwendet, ist das für die Augen genauso störend und anstrengend wie ein helles Bild oder dicker weißer Text.

Wenn Kontrast das Gleichgewicht ist, das wir anstreben, dann denken Sie daran, dass der Dark Mode mehr als nur Schwarz und Grau ist. Wie wäre es mit einem dunkelblauen Hintergrund mit blassgelbem Text? Oder Dunkelbraun mit Beige? Es gibt ein ganzes (und wachsendes) Farbspektrum da draußen, und wir können jeden Teil davon nutzen, um die Kreativität anzuregen.

Einige Beispiele für Farben, die dunkel sind, ohne auf reines Schwarz zurückzugreifen

#232B32

#152028

#202945

Material Designs Leitfaden zum Dark Mode ist eine nützliche Ressource für Best Practices. Er ist definitiv lesenswert für weitere Tipps, die man im Hinterkopf behalten sollte.

Dark Mode in der Praxis

YouTube verwendet die Technik der CSS-Variablen. Sie haben alle ihre Farben in Variablen unter dem html-Selektor definiert, während Dark-Mode-Farben unter html:not(.style-scope)[dark] definiert sind. Wenn der Dark Mode aktiviert ist, fügt YouTube dem <html>-Tag ein Attribut dark="true" hinzu. Dies nutzen sie, um die im HTML definierten Variablen zu überschreiben.

YouTube fügt dem <html> das Attribut dark=true hinzu, wenn es in den Dark Mode wechselt.

In der Praxis scheint der Ansatz mit CSS Custom Properties am beliebtesten zu sein. Er wird von Dropbox Paper, Slack und Facebook verwendet.

Simplenote verwendet die Methode des Klassentauschs, bei der alle hellen Stilregeln Nachfahren einer Elternklasse .theme-light sind und alle dunklen Stile unter eine .theme-dark-Klasse fallen. Wenn das Theme umgeschaltet wird, wird die entsprechende Klasse auf den <body>-Tag angewendet.

Simplenote verwendet zwei Klassen: .light-theme und .dark-theme, um die Themes zu gestalten.

Twitter geht noch einen Schritt weiter und bietet mehrere Themes zur Auswahl an: „Standard“, „Dämmerung“ (Dim) und „Licht aus“ (Lights out). Die Option „Dämmerung“ verwendet Dunkelblau als Hintergrundfarbe. Im Vergleich dazu nutzt „Licht aus“ ein tiefes Schwarz.

Twitter bietet drei Themes zur Auswahl an.

Dark Mode oder kein Dark Mode? Das ist hier die Frage.

Es gibt auf beiden Seiten vollkommen berechtigte Gründe. Einige dieser Gründe gehen sogar über den Rahmen der User Experience hinaus und beinhalten Dinge wie Timing, Budget und Ressourcen.

Obwohl man berücksichtigen sollte, warum man einen Dark Mode eventuell nicht implementieren möchte, sind hier Gründe, die dafür sprechen:

  • Es ist cool und trendy (obwohl das allein kein Grund ist, es zu tun).
  • Es verbessert die Barrierefreiheit, indem es Nutzer unterstützt, die bei extrem hellen Themes empfindlich auf Augenbelastung reagieren.
  • Es erlaubt den Nutzern, selbst über die angenehmste Art des Inhaltskonsums zu entscheiden, während wir gleichzeitig die Kontrolle über das Look-and-Feel behalten. Denken Sie daran: Wir wollen den Reader-Mode-Button schlagen!
  • Es hilft, die Akkulaufzeit bei Geräten mit OLED-Bildschirmen zu schonen, bei denen hellere Farben mehr Energie verbrauchen.
  • Es ist extrem beliebt und wird so schnell nicht verschwinden. Es ist wahrscheinlich, dass Ihre Nutzer, die einen Dark Mode bevorzugen (so wie ich!), erwarten, dass Ihre Seite einen hat. Man sollte also besser darauf vorbereitet sein.