Spis treści
- Wprowadzenie
- Zanim zaczniemy
- Tryb ciemny w React
- Podsumowanie
Wprowadzenie
Tryb ciemny jest standardowym rozwiązaniem w nowoczesnych aplikacjach jak Github. Zmianę trybu na bardziej sprzyjający używaniu komputera po ciemku wspierają już natywnie systemy operacyjne. Na coraz większej ilości stron internetowych, między innymi dokumentacji Material UI, dostępne są przyciski przełączające między trybem dziennym i nocnym.
Sam zapewne używasz trybu ciemnego w swoim IDE, jak większość programistów.
Warto więc wiedzieć, jak w prosty sposób zabrać się za implementację tych trybów w Twojej aplikacji, zwłaszcza że – spoiler alert! – istnieje możliwość sprawdzenia z poziomu CSS i JS, czy system operacyjny użytkownika działa w trybie nocnym i do tego dostosować wyświetlanie w aplikacji.
Najpierw omówimy potrzebne tematy z osobna, a potem wszystko zgrabnie połączymy w prosty przykład obsługi trybu jasnego i ciemnego.
Zanim zaczniemy
React Context
React Context przyda nam się do przetrzymywania tego, jaki tryb wybrał użytkownik. Jeśli wcześniej nie miałeś z nim do czynienia, polecam Ci zajrzeć do postu w którym Context jest pokrótce opisany – Context jako jedna z możliwych alternatyw dla Reduxa:
CSS Custom Properties (aka CSS Variables)
Czym są css custom properties?
CSS pozwala na definiowanie zmiennych dzięki funkcjonalności o nazwie CSS Custom Properties, która często jest nazywana CSS Variables. W skrócie chodzi o to, że jeżeli zadeklarujemy zmienną, np w zakresie globalnym:
:root { --primary-color: blue; }
To potem możemy używać tej zmiennej unikając wielu powtórzeń w kodzie. Kod klasyczny wyglądałby następująco:
.button { background-color: blue; } .input { border-color: blue; } .header { color: blue; }
Natomiast kod używający CSS Variables tak:
.button { background-color: var(--primary-color); } .input { background-color: var(--primary-color); } .header { background-color: var(--primary-color); }
Dzięki temu że używamy wszędzie tej samej zmiennej, w prosty sposób można zaimplementować np zmiany kolorystyki szablonów. Dodatkowo, można to zrobić zarówno z poziomu CSS jak i JS, dzięki czemu te wartości mogą przyjść z backendu – na przykład administrator strony może zmieniać jej kolorystykę bez konieczności zdeployowania nowej wersji aplikacji poprzez zmianę wartości w JSON jaki zwraca backend. Zmiana w CSS mogłaby wyglądać tak:
:root { --primary-color: blue; } body.green-theme { --primary-color: green; } body.red-theme { --primary-color: red; }
A sama klasa do body
dodawana byłaby przez JS.
Natomiast chcąc zmienić wartość zmiennej bezpośrednio z kodu JS mogłoby to wyglądać tak:
const response = await fetch(API_URL); const data = await response.json(); document .querySelector('body') .style .setProperty('--primary-color', data.primaryColor);
Przykład użycia CSS Variables
Stwórzmy oparty na 3 sliderach przykład pokazujący jak użyć CSS Variables do zmiany koloru elementu. Każdy ze sliderów będzie odpowiadał za jeden z palety kolorów RGB. Stwórzmy więc kod CSS:
:root { --red-value: 0; --green-value: 0; --blue-value: 0; } .result { width: 50px; height: 50px; background-color: rgb(var(--red-value), var(--green-value), var(--blue-value)); }
Kod HTML zawierał będzie 3 sildery utworzone za pomocą elementu input typu range:
<div id="bubble-wrapper"> <label for="red">Red</label> <input type="range" min="0" max="255" step="1" value="0" id="red"> <br> <label for="green">Green</label> <input type="range" min="0" max="255" step="1" value="0" id="green"> <br> <label for="blue">Blue</label> <input type="range" min="0" max="255" step="1" value="0" id="blue"> </div> <br> Result: <div class="result"></div>
Wrapper potrzebny jest nam po to, by zamiast podpinać się pod event „input” trzykrotnie, osobno dla każdego slidera – podpiąć się pod ten event tylko na jednym elemencie. Ponieważ event „input” propaguje się w górę możemy podpiąć się pod niego w następujący sposób:
document.querySelector('#bubble-wrapper').addEventListener('input', e => { if (!['red', 'green', 'blue'].includes(e.target.id)) { return; } document .querySelector('body') .style .setProperty(`--${e.target.id}-value`, e.target.value); });
Efektem działania tego kodu będzie zmiana wartości zmiennych na elemencie body
:

Kod przykładu na Github
Kod przykładu znajdziesz na:
https://github.com/radek-anuszewski/css-variables-demo
Interaktywny przykład na Github Pages
Jeżeli chcesz zobaczyć jak kod działa w praktyce, możesz odwiedzić Github Pages:
https://radek-anuszewski.github.io/css-variables-demo/index.html
Prefers-color-scheme
Czym jest prefers-color-scheme?
Prefers-color-scheme to funkcjonalność pozwalająca wykryć jaki schemat kolorów, jasny czy ciemny, ma ustawiony użytkownik w swoim systemie operacyjnym. Deklaracja wygląda w następujący sposób:
@media (prefers-color-scheme: dark) { :root { --background-color: black; --text-color: white; } } @media (prefers-color-scheme: light) { :root { --background-color: white; --text-color: black; } }
Oczywiście wewnątrz deklaracji prefers-color-scheme
można pisać dowolne style, nie tylko zmienne.
Przykład użycia prefers-color-scheme
Napiszmy prosty przykład użycia tej funkcjonalności – kwadrat z tekstem w środku, gdzie dla trybu ciemnego tło będzie czarne a tekst biały, a dla trybu jasnego odwrotnie. Stwórzmy style które będą zmieniać kolory w zależności od schematu kolorów:
:root { --background-color: red; --text-color: red; } @media (prefers-color-scheme: dark) { :root { --background-color: black; --text-color: white; } } @media (prefers-color-scheme: light) { :root { --background-color: white; --text-color: black; } }
Sam element, kwadrat o boku 300px, zawierąjacy tekst, ostylowany będzie następująco:
.result { width: 200px; height: 200px; display: flex; justify-content: center; align-items: center; margin-top: 20px; background-color: var(--background-color); color: var(--text-color); }
Kod HTML będzie bardzo prosty:
If you set dark color scheme in your OS, you'll se black background and white text. <br> If not, you'll see white background and black text. <div class="result"> Result </div>
Ponieważ mam włączony tryb ciemny, wyświetla się u mnie czarny kwadrat z białym tekstem:

Kod przykładu na Github
Kod przykładu dostępny jest pod adresem:
https://github.com/radek-anuszewski/prefers-color-scheme-demo
Interaktywny przykład na Github Pages
Jeśli chcesz sprawdzić działanie na Twoim urządzeniu odwiedź adres:
https://radek-anuszewski.github.io/prefers-color-scheme-demo/index.html
Tryb ciemny w React
Przykład
Połączmy teraz wszystkie elementy w jedną całość w przykładzie, który zawierał będzie możliwość przełączania się pomiędzy trybem jasnym, ciemnym i domyślnym ustawieniem systemu.
Do stylowania aplikacji użyjemy CSS Modules, które jest domyślnie wspierane w create-react-app i o którym wspominam w artykule Poznaj 5 Sposobów Stylowania Komponentów React w 2020:
Bazując na przykładzie prefers-color-scheme
dodajmy ustawianie zmiennych CSS dla następujących przypadków:
- Tryb jasny
- Tryb ciemny
- Klasa
light
- Klasa
dark
Kod prezentuje się następująco:
:root { --background-color: white; --color: black; } @media (prefers-color-scheme: light) { :root { --background-color: white; --font-color: black; } } @media (prefers-color-scheme: dark) { :root { --background-color: black; --font-color: white; } } .light { --background-color: white; --font-color: black; } .dark { --background-color: black; --font-color: white; }
Stwórzmy Context który na poziomie globalnym będzie przetrzymywał informacje o tym jaki tryb jest wybrany oraz customowy hook, który uprości odpytywanie o te wartości w komponentach:
const ThemeContext = createContext({ theme: 'No theme set', setTheme: () => {}, }); const useThemeContext = () => useContext(ThemeContext);
Do zmiany wartości będą służyć przyciski, z których każdy dostanie wartość jaką będzie ustawiał. Dodatkowo, jeżeli jego wartość będzie taka sama jak ustawiona w Context, będzie on wyłączony:
const Button = ({value}) => { const { theme, setTheme } = useThemeContext() return ( <button onClick={() => setTheme(value)} disabled={theme === value} > {value.toUpperCase()} </button> ) } const Buttons = () => { return ( <> <Button value={'light'} /> <Button value={'dark'} /> <Button value={'default'} /> </> ) }
Klasa ustawiająca wartość zmiennych, bądź jej brak wyliczana będzie na podstawie obecnie wybranego trybu:
import appStyles from './App.module.css'; const className = theme === 'default'? '' : theme === 'light'? appStyles.light : appStyles.dark;
CSS modules dla o unikalność nazw klas, dlatego importujemy style jako obiekt, gdzie nazwom klas odpowiadają nazwy kluczy tego obiektu.
Same zmienne natomiast są używane do stylowania „wiadomości”:
.message { /* style wygladu*/ background-color: var(--background-color); color: var(--font-color); }
Pełny kod do obejrzenia będzie na Githubie i do przeklikania na Github Pages, jego rezultat przy domyślnym trybie ciemnym będzie następujący:

Natomiast po przełączenia na tryb jasny:

Kod przykładu na Github
Kod przykładu możesz znaleźć pod adresem:
https://github.com/radek-anuszewski/dark-mode-context-demo
Interaktywny przykład na Github Pages
Klikalną wersję aplikacji znajdziesz tutaj:
https://radek-anuszewski.github.io/dark-mode-context-demo/
Podsumowanie
Wpis ten przeprowadził Cię przez napisanie prostej aplikacji zawierającej tryb jasny i ciemny. Choć prawdziwa produkcyjna aplikacja miałaby zapewne parę kruczków, gdzie do obsługi tych trybów wymagany byłby jakiś dedykowany kod, znaczna część przełączania miedzy trybem jasnym i ciemnym może zadziać się automatycznie.
Jeden komentarz do “Sprawdź jak zaimplementować tryb ciemny w React”
Możliwość komentowania została wyłączona.