Tryb ciemny to już nie fanaberia
Jeszcze kilka lat temu dark mode był ciekawostką dostępną głównie w edytorach kodu. Dziś to standard — systemy operacyjne, przeglądarki i większość popularnych aplikacji oferują tryb ciemny. Użytkownicy go oczekują, a poprawna implementacja wpływa na komfort korzystania ze strony, zwłaszcza wieczorem i w nocy.
Jak zaimplementować dark mode poprawnie, bez bałaganu w kodzie i z poszanowaniem preferencji użytkownika? Oto nasz sprawdzony przepis.
Krok 1: CSS Custom Properties jako fundament
Podstawą elastycznego systemu kolorów są zmienne CSS (CSS Custom Properties). Zamiast hardkodować kolory w dziesiątkach miejsc, definiujemy je raz i używamy w całym arkuszu stylów.
:root {
--color-bg: #ffffff;
--color-text: #1a1a2e;
--color-heading: #16213e;
--color-accent: #0f3460;
--color-surface: #f5f5f5;
--color-border: #e0e0e0;
}
Następnie używamy tych zmiennych zamiast bezpośrednich wartości kolorów:
body {
background-color: var(--color-bg);
color: var(--color-text);
}
h1, h2, h3 {
color: var(--color-heading);
}
Ta struktura sprawia, że zmiana motywu sprowadza się do nadpisania kilku zmiennych — nie trzeba duplikować reguł CSS.
Krok 2: Automatyczne wykrywanie preferencji systemowych
Media query prefers-color-scheme pozwala wykryć, czy użytkownik ma włączony tryb ciemny na poziomie systemu operacyjnego:
@media (prefers-color-scheme: dark) {
:root {
--color-bg: #1a1a2e;
--color-text: #e0e0e0;
--color-heading: #eaeaea;
--color-accent: #4fc3f7;
--color-surface: #16213e;
--color-border: #2a2a4a;
}
}
To rozwiązanie działa automatycznie — bez żadnego JavaScript. Jeśli użytkownik ma w systemie włączony dark mode, strona od razu wyświetli się w ciemnym motywie. To powinien być absolutne minimum każdej implementacji.
Krok 3: Przełącznik ręczny
Automatyczne wykrywanie to świetny start, ale użytkownicy cenią sobie możliwość ręcznego przełączania. Niektórzy wolą dark mode w przeglądarce, choć system mają w trybie jasnym.
Najprostsze podejście polega na dodaniu klasy do elementu <html>:
html[data-theme="dark"] {
--color-bg: #1a1a2e;
--color-text: #e0e0e0;
--color-heading: #eaeaea;
--color-accent: #4fc3f7;
--color-surface: #16213e;
--color-border: #2a2a4a;
}
A obsługa przełącznika w JavaScript:
const toggle = document.getElementById('theme-toggle');
toggle.addEventListener('click', () => {
const current = document.documentElement.getAttribute('data-theme');
const next = current === 'dark' ? 'light' : 'dark';
document.documentElement.setAttribute('data-theme', next);
localStorage.setItem('theme', next);
});
Krok 4: Zapisywanie i odtwarzanie preferencji
Kluczowe jest, aby wybór użytkownika przetrwał odświeżenie strony. Używamy do tego localStorage:
function getPreferredTheme() {
const saved = localStorage.getItem('theme');
if (saved) return saved;
return window.matchMedia('(prefers-color-scheme: dark)').matches
? 'dark'
: 'light';
}
document.documentElement.setAttribute('data-theme', getPreferredTheme());
Ważne: ten skrypt powinien być umieszczony w <head> strony, najlepiej jako inline <script>, a nie jako zewnętrzny plik. Dlaczego? Aby uniknąć efektu flashowania (FOUC — Flash of Unstyled Content), gdzie strona na ułamek sekundy wyświetla się w jasnym motywie, zanim JavaScript zmieni schemat kolorów.
Krok 5: Reagowanie na zmiany systemowe
Użytkownik może zmienić motyw systemowy w trakcie przeglądania strony. Warto na to reagować — ale tylko wtedy, gdy nie nadpisał preferencji ręcznie:
window.matchMedia('(prefers-color-scheme: dark)')
.addEventListener('change', (e) => {
if (!localStorage.getItem('theme')) {
document.documentElement.setAttribute(
'data-theme',
e.matches ? 'dark' : 'light'
);
}
});
Najczęstsze błędy przy implementacji dark mode
Na podstawie naszych doświadczeń zebraliśmy listę pułapek, w które łatwo wpaść:
- Za niski kontrast tekstu — ciemne tło nie oznacza, że tekst może być szary. Minimalny współczynnik kontrastu to 4.5:1 dla normalnego tekstu (standard WCAG AA). Narzędzie takie jak WebAIM Contrast Checker pomoże to zweryfikować
- Czysto biały tekst na czarnym tle — to zbyt agresywne dla oczu. Lepiej użyć lekkiego odcienia szarości (
#e0e0e0) na ciemnogranatowym tle (#1a1a2e) - Zapominanie o obrazkach — zdjęcia i grafiki mogą wyglądać dziwnie na ciemnym tle. Rozważ dodanie delikatnego zaokrąglonego obramowania lub cienia
- Brak testowania formularzy — pola input, selecty i przyciski często wymagają osobnych stylów dla dark mode
- Hardkodowane kolory w komponentach — jeden zapomniany
color: #333w zagnieżdżonym komponencie potrafi zepsuć cały motyw
Obrazki i multimedia
Dla grafik SVG osadzonych inline zmiana kolorów jest prosta — wystarczy użyć tych samych zmiennych CSS. Dla bitmap (PNG, JPG) warto rozważyć:
@media (prefers-color-scheme: dark) {
img.invertible {
filter: brightness(0.9) contrast(1.1);
}
img.logo-light {
display: none;
}
img.logo-dark {
display: block;
}
}
Alternatywnie, element <picture> pozwala serwować różne wersje obrazka w zależności od motywu:
<picture>
<source srcset="logo-dark.png"
media="(prefers-color-scheme: dark)">
<img src="logo-light.png" alt="Logo">
</picture>
Podsumowanie
Prawidłowa implementacja dark mode to nie jest kwestia jednego media query. To przemyślany system kolorów oparty na zmiennych CSS, automatyczne wykrywanie preferencji systemowych, ręczny przełącznik z zapisem do localStorage i eliminacja flashowania przy ładowaniu strony.
Zacznij od zdefiniowania pełnej palety kolorów w zmiennych CSS, dodaj media query prefers-color-scheme, a dopiero potem dobuduj przełącznik ręczny. Testuj w obu trybach na każdym etapie pracy — a Twoi użytkownicy będą wdzięczni za komfortowe przeglądanie strony o każdej porze dnia.
Potrzebujesz pomocy z implementacją dark mode lub innego elementu frontendu? Skontaktuj się z nami.