Pierwszy system projektowy, który zbudowałem, wypuszczono z dokładnie jednym błędem, który prześladował wszystko, co nastąpiło później: użyliśmy blue-500 bezpośrednio w kodzie komponentów. Przyciski były blue-500. Linki były blue-500. Pierścień fokusu był blue-500. Wyglądało to czysto i zgodnie z DRY. Potem marketing przeprowadził rebranding z niebieskiego na morską zieleń, a my spędziliśmy dwa sprinty, ścigając każde dosłowne odwołanie hex w czterdziestu repozytoriach — i wciąż kilku nie wyłapaliśmy, bo niektóre zostały zakodowane na sztywno jako #3B82F6, a nie jako zmienna. Cały ten epizod mógł być zmianą w jednej linii. Powodem, dla którego nie był, jest cała racja istnienia tokenów kolorów systemu projektowego.
Token koloru to po prostu nazwane odwołanie do wartości koloru. Ale wartość tokenów nie tkwi w nazewnictwie — tkwi w warstwach nazewnictwa i w dyscyplinie co do tego, której warstwy komponent ma prawo dotknąć. Trafnie ułóż warstwy, a rebranding, start trybu ciemnego czy poprawka kontrastu staną się małą, ograniczoną zmianą. Pomyl się, a dostaniesz sprzątanie czterdziestu repozytoriów.
Trzy warstwy: prymitywna, semantyczna, komponentowa
Model myślowy, który wytrzymał w każdym systemie, nad jakim pracowałem, to trójpoziomowa hierarchia. Każdy poziom odwołuje się do tego poniżej, a komponenty mają prawo konsumować tylko poziom najwyższy.
- Tokeny prymitywne (zwane też bazowymi lub globalnymi) to twoja surowa paleta.
blue-500: #3B82F6,gray-900: #111827,red-600: #DC2626. Opisują, czym kolor jest, i nic na temat tego, gdzie się go używa. Prymityw nigdy nie powinien pojawiać się bezpośrednio w CSS przycisku. - Tokeny semantyczne opisują intencję — zadanie, które kolor wykonuje.
color-text-primary,color-bg-surface,color-border-default,color-action-primary,color-feedback-error. Token semantyczny wskazuje na prymityw:color-action-primary → blue-600. - Tokeny komponentowe są opcjonalne i opisują konkretną część konkretnego komponentu.
button-primary-bg,card-border-color,input-focus-ring. Wskazują na tokeny semantyczne, nie na prymitywy.
Oto zasada, która sprawia, że całość działa, i ta, którą większość zespołów pomija: komponenty odwołują się do tokenów semantycznych lub komponentowych, nigdy do prymitywów. W chwili, gdy przycisk sięga ponad warstwę semantyczną, by chwycić blue-500 bezpośrednio, ponownie wprowadzasz problem czterdziestu repozytoriów — po jednym komponencie naraz.
Po co w ogóle zawracać sobie głowę semantyczną warstwą pośrednią? Bo to szew, w którym dzieje się zmiana motywu. Gdy przełączasz na tryb ciemny, nie przemalowujesz każdego komponentu — przekierowujesz kilkadziesiąt tokenów semantycznych na inne prymitywy. Komponenty nie wiedzą, że cokolwiek się zmieniło. Ta pośredniość to pojedyncza decyzja o najwyższej dźwigni w całym systemie.
Zbuduj skalę, zanim cokolwiek nazwiesz
Przed semantyką potrzebujesz prymitywów wartych wskazywania — a to oznacza skalę liczbową, umownie od 50 do 950. Konwencja spopularyzowana przez Tailwind (50, 100, 200 … 900, 950) stała się faktycznie branżowym standardem i warto ją przyjąć, choćby po to, by nowi inżynierowie czuli się jak u siebie. 50 to najjaśniejszy odcień, 500 to z grubsza czysty odcień marki, 950 to niemal czerń.
Błąd, który popełniają początkujący, to generowanie skali przez naiwne rozjaśnianie i przyciemnianie w sRGB — dodawanie bieli na odcienie, czerni na cienie. Dostajesz błotniste półtony i stopnie, które nie wydają się równomiernie rozłożone, bo postrzegana jasność nie jest liniowa w RGB. 400 kończy wyglądając niemal identycznie jak 500, podczas gdy od 700 do 800 jest przepaść. Lekarstwem jest rozstawianie stopni w modelu percepcyjnym. Narzędzia pracujące w HSL doprowadzą cię większość drogi; narzędzia używające OKLCH (obecnie dobrze wspieranego w CSS) doprowadzą cię na resztę, bo OKLCH utrzymuje postrzeganą jasność spójną przy zmianie odcienia. Jeśli chcesz szybko oceniać harmonijne odcienie i cienie na oko podczas prototypowania skali, generator palety kolorów to szybki sposób na zobaczenie relacji między odcieniami, zanim zatwierdzisz je w tokenach.
Kilka okupionych potem zasad dla skal:
- Zakotwicz skalę w kontraście, nie w estetyce. Zdecyduj wcześnie, który stopień jest twoim kolorem „tekst na bieli”, i sprawdź, czy trafia we współczynniki, których potrzebujesz. W większości neutralnych ramp to
600lub700jest miejscem, gdzie przekraczasz 4,5:1 względem bieli. Wiedza o tym pozwala pisać zasady semantyczne w stylu „tekst zasadniczy używagray-700” z pewnością. - Trzymaj tę samą liczbę stopni w każdym odcieniu. Jeśli niebieski ma
950, a zielony zatrzymuje się na900, twoje mapowania semantyczne robią się nieregularne, a tryb ciemny staje się koszmarem przypadków szczególnych. - Nie przesadzaj z generowaniem. Jedenaście stopni na odcień to mnóstwo. Widziałem zespoły wypuszczające dwudziestostopniowe skale, gdzie połowa stopni jest wizualnie nierozróżnialna i nikt nigdy nie używa
425.
Nazewnictwo: opisuj zadanie, nie wygląd
Najważniejsza zasada nazewnicza brzmi tak: tokeny semantyczne i komponentowe powinny nazywać przeznaczenie, nigdy wygląd. Token nazwany color-text-red psuje się w dniu, w którym tekst błędu staje się pomarańczowy — nazwa zaczyna kłamać. Token nazwany color-feedback-error przetrwa tę zmianę nietknięty, bo „błąd” pozostaje prawdą niezależnie od tego, na którą czerwień (czy pomarańcz) się rozwiązuje.
Struktura nazewnicza, która się skaluje, czytana jest od szerokiej kategorii do szczegółowego modyfikatora, bo sortuje się i audytuje czysto:
category-property-variant-statecolor-bg-surface,color-bg-surface-raised,color-bg-surface-sunkencolor-action-primary,color-action-primary-hover,color-action-dangercolor-text-primary,color-text-secondary,color-text-disabled,color-text-on-action
Ten ostatni — color-text-on-action — to token, o którym ludzie zapominają i tego żałują. Gdy twój kolor akcji to nasycony niebieski, tekst na tym niebieskim musi być biały lub niemal biały, a ta para nie ma nic wspólnego z color-text-primary. Uczyń go jawnym tokenem, bo inaczej będziesz patrzył, jak inżynierowie kodują na sztywno #FFFFFF na przyciskach, a potem zgłaszają błędy dostępności, gdy ktoś wprowadzi jasnożółtą akcję „ostrzeżenie”.
W przypadku prymitywów nazywanie wyglądu jest poprawne i oczekiwane — blue-500 powinien opisywać, czym jest, bo to cały sens warstwy prymitywnej. Zasada „opisuj intencję” obowiązuje powyżej niej.
Implementacja z właściwościami niestandardowymi CSS
Właściwości niestandardowe CSS to naturalny dom dla tokenów, bo kaskadują i mogą być nadpisywane przez kontekst — a dokładnie tak działa zmiana motywu. Wzorzec to dwie warstwy zmiennych: prymitywy zdefiniowane raz w korzeniu, semantyki zdefiniowane w korzeniu i redefiniowane pod selektorem motywu.
Definiuj prymitywy globalnie — --blue-600: #2563EB, --gray-900: #111827 i tak dalej. Potem mapuj na nie semantyki: --color-action-primary: var(--blue-600) oraz --color-text-primary: var(--gray-900). Komponenty czytają tylko zmienne semantyczne: color: var(--color-text-primary). Nigdy nie nazywają prymitywu i nigdy nie nazywają kodu hex.
W Tailwind v4 mapuje się to czysto na nową dyrektywę @theme, która zarówno deklaruje tokeny, jak i generuje z nich klasy użytkowe, eksponując każdy token jako runtime'ową zmienną CSS. Definiujesz --color-text-primary: var(--color-gray-900) wewnątrz @theme, używasz text-primary w znacznikach i nadpisujesz zmienną pod selektorem .dark w warstwie bazowej. Oficjalna dokumentacja zmiennych motywu Tailwind opisuje mechanikę, a ta sama dwuwarstwowa dyscyplina obowiązuje niezależnie od frameworka.
Tryb ciemny to problem mapowania tokenów, nie problem koloru
Powodem, dla którego warstwa semantyczna zarabia na siebie, jest to, że tryb ciemny staje się niemal trywialny, gdy już istnieje. Nie dotykasz ani jednego komponentu. Nadpisujesz tokeny semantyczne wewnątrz zakresu .dark (lub [data-theme="dark"]), tak by rozwiązywały się do innych prymitywów.
W trybie jasnym --color-bg-surface: var(--white) i --color-text-primary: var(--gray-900). W trybie ciemnym, pod selektorem .dark, --color-bg-surface: var(--gray-900) i --color-text-primary: var(--gray-100). Każdy komponent, który czyta color-bg-surface, przełącza się automatycznie, bo robotę wykonuje kaskada.
Dwie rzeczy, które tu gryzą:
- Nie odwracaj po prostu skali. Czysto biały tekst na czystej czerni (
#000na odwróconym#FFF) jest ostry i powoduje halację — to rozmazane świecenie wokół tekstu na ekranach OLED. Ciemne powierzchnie powinny być ciemnoszare, jak#121212do#1A1A1A, a twój najjaśniejszy tekst miękką złamaną bielą, a nie#FFFFFF. Dlatego twoja skala potrzebuje prawdziwego950i50, a nie dosłownej czerni i bieli na końcach. - Odbarw akcenty na ciemne tła.
blue-600, który wygląda pewnie na bieli, może niewygodnie wibrować na niemal czarnej powierzchni. Ciemne motywy zwykle wskazują swój token akcji na jaśniejszy, nieco mniej nasycony stopień —blue-400zamiastblue-600. Ponieważ to przekierowanie jednego tokena, możesz dostroić to globalnie w kilka sekund.
Pułapki, które naprawdę rozbijają system przy skali
Po dostatecznej liczbie rebrandingów i startów motywów schematy zawodności się rymują:
- Przeciekanie prymitywów do komponentów. Grzech pierworodny. Lintuj to — zasada zakazująca nazw tokenów prymitywnych i surowego hex w plikach komponentów warta jest napisania pierwszego dnia.
- Tokeny semantyczne, które potajemnie kodują wartość.
color-blue-actionto prymityw w przebraniu semantycznym. Jeśli nazwa zawiera odcień, to nie jest naprawdę semantyczna i pęknie przy rebrandingu. - Pomijanie warstwy semantycznej „żeby przyspieszyć”. Komponenty związane wprost z prymitywami wydają się w porządku aż do twojego pierwszego motywu. Wtedy refaktoryzujesz pod presją terminu — w najdroższym momencie, by to robić.
- Kontrast testowany tylko w trybie jasnym. Para, która przechodzi 4,5:1 na bieli, może zawieść na ciemnej powierzchni i odwrotnie. Każda semantyczna para tekst-na-tle wymaga sprawdzenia w każdym motywie. Progi WCAG to 4,5:1 dla tekstu normalnego i 3:1 dla tekstu dużego oraz komponentów UI, zgodnie z wytycznymi kontrastu WCAG od W3C — a twój system tokenów to miejsce, gdzie powinieneś wbudować te gwarancje, a nie odkrywać naruszenia.
- Za dużo tokenów. System z czterystoma tokenami semantycznymi jest tak samo bezużyteczny jak ten z czterema. Jeśli dwa tokeny zawsze rozwiązują się do tej samej wartości i zawsze będą, to jeden token. Dodawaj szczegółowość tylko wtedy, gdy pojawia się realna rozbieżność.
Uczciwe podsumowanie jest takie, że tokeny nie podejmują za ciebie decyzji kolorystycznych — czynią decyzje kolorystyczne tanimi do zmiany. To struktura kupuje ci możliwość przeprowadzenia rebrandingu w piątek, wypuszczenia trybu ciemnego w sprincie i naprawienia błędu kontrastu w jednej linii zamiast w czterdziestu repozytoriach. Włóż wysiłek w warstwę semantyczną i nazewnictwo, trzymaj komponenty uczciwe co do tego, którego poziomu dotykają, a system wchłonie zmiany, których jeszcze nawet nie potrafisz przewidzieć.
Najczęściej zadawane pytania
Jaka jest różnica między prymitywnymi a semantycznymi tokenami kolorów?
Token prymitywny (lub bazowy) nazywa surową wartość koloru bez kontekstu — na przykład blue-500: #3B82F6. Opisuje, czym kolor jest. Token semantyczny nazywa przeznaczenie koloru, jak color-action-primary czy color-feedback-error, i wskazuje na prymityw. Komponenty powinny odwoływać się do tokenów semantycznych, nigdy do prymitywów, tak aby rebranding lub zmiana motywu wymagały jedynie przekierowania warstwy semantycznej, a nie edytowania każdego komponentu.
Jak powinienem nazywać tokeny kolorów w systemie projektowym?
Nazywaj tokeny semantyczne i komponentowe według intencji, a nie wyglądu. color-feedback-error przetrwa redesign, w którym czerwień błędu staje się pomarańczem; color-text-red nie. Użyj struktury od ogólnej do szczegółowej, jak category-property-variant-state (np. color-bg-surface-raised, color-action-primary-hover). Tokeny prymitywne są wyjątkiem — powinny opisywać dosłowny kolor, jak gray-900, bo to jest cały ich cel.
Dlaczego stosować liczbową skalę 50-950?
Skala 50-950 (50, 100, 200 … 900, 950) spopularyzowana przez Tailwind stała się branżowym standardem, więc jest natychmiast znajoma dla nowych inżynierów. 50 to najjaśniejszy odcień, ~500 to czysty odcień marki, a 950 to niemal czerń. Rozstawianie stopni w modelu percepcyjnym jak HSL lub OKLCH — zamiast naiwnego mieszania bieli i czerni w sRGB — sprawia, że stopnie wydają się równomiernie rozłożone i unika błotnistych półtonów.
Jak tokeny kolorów ułatwiają tryb ciemny?
Tryb ciemny staje się problemem mapowania tokenów, a nie redesignu. Ponieważ komponenty czytają tylko tokeny semantyczne, nadpisujesz te tokeny wewnątrz zakresu .dark lub [data-theme=dark], tak by rozwiązywały się do innych prymitywów — na przykład color-bg-surface wskazuje na biel w trybie jasnym i gray-900 w ciemnym. Kaskada CSS przełącza każdy komponent automatycznie, bez zmian w kodzie komponentów. Pamiętaj, by odbarwić kolory akcentowe i unikać czysto czarnych teł, by zmniejszyć zmęczenie oczu i halację.
Jakie współczynniki kontrastu muszą spełniać tokeny kolorów dla dostępności?
WCAG wymaga współczynnika kontrastu co najmniej 4,5:1 dla tekstu normalnego oraz 3:1 dla tekstu dużego i komponentów UI. Wbuduj te kontrole w swój system tokenów, walidując każdą semantyczną parę tekst-na-tle w każdym motywie. Kombinacja, która przechodzi w trybie jasnym, może zawieść w ciemnym, więc testuj oba i zdefiniuj jawny token text-on-action, by etykiety na nasyconych przyciskach pozostały czytelne.
Chcesz poeksperymentować z kolorami?
Wypróbuj nasz darmowy generator palet kolorów, aby znaleźć idealną harmonię — z wbudowanym sprawdzaniem kontrastu WCAG.
Otwórz generator