Abschnitt 12 · Lektion 6
Dunkelmodus und jeder Bildschirm
Ein Dunkelmodus und ein Layout von 320px bis Desktop haben scheinbar nichts miteinander zu tun. Beide machen aber fast keine Arbeit mehr, wenn die Vorarbeit stimmt, und hier kommen sie zusammen.
Warum die Tokens Rollen tragen
<p class="titel">dieselbe Karte mit color-scheme: light</p>
<div class="huelle hell">
<div class="karte">
<h3>Tipptrainer</h3>
<p>Zehnfinger im Browser, ohne Anmeldung.</p>
<a class="button" href="#">Ansehen</a>
</div>
</div>
<p class="titel">und mit color-scheme: dark</p>
<div class="huelle dunkel">
<div class="karte">
<h3>Tipptrainer</h3>
<p>Zehnfinger im Browser, ohne Anmeldung.</p>
<a class="button" href="#">Ansehen</a>
</div>
</div> *,
*::before,
*::after {
box-sizing: border-box;
}
body {
margin: 0;
padding: 16px;
font-family: system-ui, sans-serif;
line-height: 1.5;
background-color: #a1a1aa;
}
.titel {
margin-bottom: 4px;
font-size: 14px;
color: #18181b;
}
/* Jedes Token trägt beide Werte. Welcher gilt,
entscheidet das color-scheme des Elements. Im echten
Projekt steht color-scheme: light dark am :root und
die Systemeinstellung wählt aus. Hier stehen zwei
feste Werte nebeneinander, damit man beides sieht. */
.huelle {
--farbe-text: light-dark(#18181b, #fafafa);
--farbe-gedaempft: light-dark(#52525b, #a1a1aa);
--farbe-flaeche: light-dark(#ffffff, #101012);
--farbe-akzent: light-dark(#2563eb, #60a5fa);
margin-bottom: 24px;
padding: 16px;
border-radius: 12px;
background-color: light-dark(#f4f4f5, #18181b);
}
.hell {
color-scheme: light;
}
.dunkel {
color-scheme: dark;
}
/* Und hier steht jede Regel genau einmal. Sie liest
nur Tokens und weiß von keinem Schema. */
.karte {
padding: 16px;
border-radius: 8px;
color: var(--farbe-text);
background-color: var(--farbe-flaeche);
}
.karte h3 {
margin: 0 0 4px 0;
}
.karte p {
margin: 0 0 16px 0;
color: var(--farbe-gedaempft);
}
.button {
display: inline-block;
padding: 8px 16px;
border-radius: 8px;
font-weight: 700;
text-decoration: none;
color: var(--farbe-flaeche);
background-color: var(--farbe-akzent);
} In Lektion 5.5 haben die Farben Rollennamen bekommen, --farbe-text, --farbe-gedaempft, --farbe-flaeche und --farbe-linie. Damals wirkte das wie eine Geschmacksfrage. Hier zahlt es sich aus.
Eine Rolle bleibt im Dunkelmodus dieselbe. Text ist weiterhin Text und die Fläche weiterhin die Fläche, nur der konkrete Wert dreht sich um. Du änderst die Tokens, und jede Regel, die sie liest, stimmt weiter.
Hieße das Token --grau-900, wäre es im Dunkelmodus das Hellste im Bild. Dann müsstest du jede Regel anfassen, die es benutzt, und jedes Mal entscheiden, ob dort Text oder Fläche gemeint war. Das ist der Unterschied zwischen einer Handvoll Zeilen und einer Woche.
light-dark()
light-dark(#ffffff, #101012) nimmt zwei Werte und wählt nach dem geltenden Farbschema einen davon aus. Erst kommt der helle, dann der dunkle. Alle großen Browser können das seit Mai 2024, Safari als letzter mit Version 17.5.
Damit trägt jedes Token beide Fassungen in einer Zeile, und es gibt keinen zweiten Block, den irgendwer an anderer Stelle mitpflegen müsste:
--farbe-flaeche: light-dark(#ffffff, #101012);
Voraussetzung ist, dass am Element ein Farbschema gilt. Das setzt color-scheme.
Was color-scheme allein schon bewirkt
<p class="titel">color-scheme: light, keine einzige Farbregel</p>
<div class="feld hell">
<input type="text" placeholder="Name">
<select><option>Bitte wählen</option></select>
<p><input type="checkbox" checked> Newsletter</p>
<textarea rows="2">Auch der Scrollbalken hier drin kommt vom color-scheme, und dafür steht keine einzige Regel im Stylesheet. Scroll mich.</textarea>
</div>
<p class="titel">color-scheme: dark, ebenfalls keine Farbregel</p>
<div class="feld dunkel">
<input type="text" placeholder="Name">
<select><option>Bitte wählen</option></select>
<p><input type="checkbox" checked> Newsletter</p>
<textarea rows="2">Auch der Scrollbalken hier drin kommt vom color-scheme, und dafür steht keine einzige Regel im Stylesheet. Scroll mich.</textarea>
</div> body {
margin: 0;
padding: 16px;
font-family: system-ui, sans-serif;
line-height: 1.5;
background-color: #a1a1aa;
}
.titel {
margin-bottom: 4px;
font-size: 14px;
color: #18181b;
}
/* An den Formularelementen steht keine einzige
Farbregel. Das Feld, die Auswahl, das Häkchen und
der Scrollbalken richten sich allein nach dem
color-scheme. Das ist Arbeit, die du nicht machen
musst und auch nicht besser machen könntest. */
.feld {
display: grid;
gap: 8px;
margin-bottom: 24px;
padding: 16px;
border-radius: 12px;
}
.hell {
color-scheme: light;
}
.dunkel {
color-scheme: dark;
}
.feld p {
margin: 0;
} color-scheme: light dark am Wurzelelement sagt dem Browser, dass die Seite beides kann. Er nimmt dann, was der Nutzer eingestellt hat.
Diese eine Zeile richtet ohne jede weitere Regel eine ganze Reihe von Dingen mit.
- die Standardfarben für Text und Hintergrund,
- alle Formularelemente, also Felder, Auswahllisten, Häkchen und Schalter,
- die Scrollbalken,
- die Fokusringe des Browsers.
Im Beispiel steht an den Formularelementen keine einzige Farbregel, und trotzdem sehen sie in beiden Schemata richtig aus. Diese Arbeit musst du nicht machen, und von Hand bekämst du sie auch nicht besser hin, weil jedes Betriebssystem seine Felder, Häkchen und Scrollbalken ein bisschen anders zeichnet.
Drehst du nur die eigenen Farben um und lässt color-scheme weg, bekommst du eine dunkle Seite mit weißen Eingabefeldern und einem weißen Scrollbalken.
Brauchst du einen eigenen Umschalter statt der Systemeinstellung, führt der Weg über prefers-color-scheme und überschriebene Tokens. Die dunklen Werte stehen dann in einem @media (prefers-color-scheme: dark)-Block, und eine Klasse am html kann das übersteuern. Das ist mehr Arbeit und nur nötig, wenn der Nutzer wirklich wählen können soll.
Dunkelmodus heißt nicht invertieren
<p class="titel">stumpf invertiert: reines Weiß auf reinem Schwarz</p>
<div class="huelle stumpf">
<div class="karte">
<h3>Tipptrainer</h3>
<p>Der Schatten ist da und man sieht ihn nicht. Der Kontrast blendet.</p>
</div>
</div>
<p class="titel">abgestimmt: gebrochene Töne, Kante statt Schatten</p>
<div class="huelle abgestimmt">
<div class="karte">
<h3>Tipptrainer</h3>
<p>Etwas weniger Kontrast, und eine helle Kante übernimmt die Aufgabe des Schattens.</p>
</div>
</div> *,
*::before,
*::after {
box-sizing: border-box;
}
body {
margin: 0;
padding: 16px;
font-family: system-ui, sans-serif;
line-height: 1.5;
background-color: #52525b;
}
.titel {
margin-bottom: 4px;
font-size: 14px;
color: #fafafa;
}
.huelle {
margin-bottom: 24px;
padding: 16px;
border-radius: 12px;
}
.karte {
padding: 16px;
border-radius: 8px;
}
.karte h3 {
margin: 0 0 4px 0;
}
.karte p {
margin: 0;
}
/* Ein Schatten ist dunkler Grund auf hellem Grund.
Auf schwarzem Grund gibt es nichts mehr, das dunkler
wäre, also ist er unsichtbar. */
.stumpf {
background-color: #000000;
}
.stumpf .karte {
color: #ffffff;
background-color: #000000;
box-shadow: 0 8px 24px rgb(0 0 0 / 40%);
}
/* Gebrochene Töne statt der Extreme, und die Trennung
übernimmt eine helle Kante. */
.abgestimmt {
background-color: #18181b;
}
.abgestimmt .karte {
color: #fafafa;
background-color: #101012;
border: 1px solid rgb(255 255 255 / 12%);
} Drei Dinge gehen kaputt, wenn man die Farben einfach umdreht.
Schatten funktionieren nicht mehr. Ein Schatten ist eine dunklere Fläche auf einer helleren. Auf schwarzem Grund gibt es nichts Dunkleres, er ist schlicht unsichtbar. Im Dunkeln trennt deshalb eine helle Kante die beiden Ebenen, also ein Rahmen mit wenigen Prozent Weiß.
Reines Weiß auf reinem Schwarz blendet. Der Kontrast ist maximal, und bei längerem Lesen flimmert der Text. Ich nehme gebrochene Töne, im Beispiel #fafafa auf #101012. Der Kontrast liegt dann immer noch weit über den geforderten 4,5 zu 1.
Bilder wirken zu grell. Ein helles Foto auf dunklem Grund sticht heraus, und ein leichtes filter: brightness(0.9) im Dunkelmodus nimmt das raus. Bei Fotos von Menschen und bei Produktbildern lasse ich allerdings die Finger davon.
Das Layout für jede Breite
Der zweite Teil kommt aus den ersten beiden Lektionen dieses Abschnitts. Die Grundform gilt für schmal, und eine Media Query legt die breite Fassung obendrauf.
Beides zusammen ergibt die Seite, an der du seit Abschnitt 4 baust. In der Aufgabe kennen ihre Farb-Tokens nur das helle Schema, und die schmale Fassung gilt auf jeder Breite.
Zum Mitnehmen
Tragen die Tokens Rollen, schaltet light-dark() in jedem Token beide Werte, und color-scheme: light dark am Wurzelelement richtet Formularelemente und Scrollbalken mit. Die Breiten kommen wie in 12.2 obendrauf.
Jetzt du
Basis Konto, kostenlosZu dieser Lektion gehört eine Aufgabe. Du schreibst den Code selbst, und nach jedem Lauf sagt dir eine Prüfliste, was schon stimmt.
Dafür brauchst du das Basis Konto. Es kostet nichts, und ein Passwort gibt es auch nicht.
Was in dieser Lektion steckt
-
Artikel mit 3 Beispielen zum Ausprobieren
Steht hier, ohne Konto lesbar.
-
Aufgabe im Editor, direkt im Browser geprüft
Öffnet sich mit dem Basis Konto.