Kolor
Techniczny: DraftAkceptacja: ApprovedKolor dobieramy przez rolę: color.background.page, color.text.primary,
color.brand.primary. Rola mówi, do czego kolor służy; wartość pod spodem
zmienia się z marką i trybem, a kod komponentu jej nie zna. Mechanikę warstw
opisują tokeny.
Każda marka ma jedną rampę po dziesięć stopni. Stopnia z rampy nie używasz
wprost. W kodzie pytasz o rolę, np. color.brand.primary, a rola wskazuje
właściwy stopień.
Numer stopnia znaczy w każdej marce to samo. Dlatego kolor marki stoi na 500 wszędzie, a tło przycisku głównego na 700 wszędzie, a nie raz na jednym stopniu, raz na innym.
Marki. Stopień podpisany „marka” to color.brand.primary — ta sama barwa,
którą narysowany jest znak. Stopień podpisany „przycisk” to color.action.primary,
czyli tło przycisku głównego; hover schodzi na 800, active na 900.
insurance#F3F7FB#E3ECF5#C5D6E8#9FB8D6#6F96C2#4A78ADmarka#355F92#274A74przycisk#1A3556#10243Dhome#FDF2F4#FADDE3#F6B9C6#F08EA4#E66A84#DE4C6Cmarka#C13A5A#962C46przycisk#6A1E32#471322health#EAF7F8#CDEDEA#A8DDD7#7CCAC3#52B7AF#2BA09Amarka#248A84#1C6D68przycisk#15524E#0E3A37moto#F3EEFC#D8CCF7#BFAAF2#A084EA#8B6AE3#7556D8marka#6347C6#5038A3przycisk#3D2A7A#2C1E58travel#EEF6FF#D6E9FF#B5D8FF#8EC2FA#66A9F0#4C90E3marka#3A78C5#2C5E9Cprzycisk#214574#182F52Navy. Rampa wspólna: niesie tła, tekst i obrysy we wszystkich pięciu
markach. Stopień 50 to jasne tło strony, 900 tekst i ciemne tło strony, 800
powierzchnia trybu ciemnego, 600 drugi poziom tekstu. Biel i czerń stoją osobno,
poza rampami. Stopnie 100, 200, 500 i 700 nie niosą dziś żadnej roli i stoją na
liście liści nieosiągalnych (tokens/unreachable.json); 700 trafił tam
2026-09-28.
wspólna#F8F9FB#F1F3F7#E6EAF0#D2D8E2#AEB6C2#8B95A3#667080#4A5361#1C2430#0B1326Turkus. Rampa marki insurance obok jej rampy głównej. Na stopniu podpisanym
„akcent” stoi color.accent.default tej marki; kilka stopni tej rampy niesie
gradient lwa w znaku. Cztery pozostałe marki mają w akcencie kolor własnego
znaku, więc własnej rampy akcentu nie mają.
akcent insurance#EEFBFA#DCF4F2#BAE8E5#8FD5D6#60BBC9#3F9BB7akcent#3183A4#266888#1B4D68#0C334AKolor znaku. Znak marki stoi na stopniu 500 rampy — tym samym, który niesie
color.brand.primary. brand.mark jest nazwą roli dla tej barwy w warstwie
marki, nie osobną wartością: zapisuje fakt o logotypie, a stopień 500 zapisuje
ten sam fakt o rampie. Nie sięga po brand.mark żadna rola semantyczna: pliki
znaku wydaje osobna paczka. W interfejsie kolorem marki jest
color.brand.primary.
Jak używać
Dział zatytułowany „Jak używać”.card { background: var(--color-background-surface); color: var(--color-text-primary); border: var(--border-width-hairline) solid var(--color-border-strong);}<div className="bg-background-surface text-text-primary border border-border-strong">…</div>Rampy bazowe (blue-600, navy-500) nie istnieją jako publiczny kontrakt
i nie pojawiają się w kodzie pod żadną postacią.
Próbka pokazuje wartość roli w marce insurance: lewy kwadrat to tryb jasny,
prawy ciemny.
Stos teł ma dwa poziomy: background.page i background.surface. Rola
background.raised zniknęła 2026-09-28 — miała identyczną wartość co surface
we wszystkich dziesięciu kombinacjach. Uniesienie niesie cień (tryb jasny)
i obrys (tryb ciemny), nie stopień koloru — reguła i pomiary stoją
w powierzchniach.
| Podgląd | Rola (token) | Zmienna CSS | Klasa Tailwind | Hex — light | Hex — dark |
|---|---|---|---|---|---|
color.background.page | --color-background-page | bg-background-page | #F8F9FB | #0B1326 | |
color.background.surface | --color-background-surface | bg-background-surface | #FFFFFF | #1C2430 | |
color.background.inverse | --color-background-inverse | bg-background-inverse | #0B1326 | #FFFFFF | |
color.text.primary | --color-text-primary | text-text-primary | #0B1326 | #F8F9FB | |
color.text.secondary | --color-text-secondary | text-text-secondary | #667080 | #AEB6C2 | |
color.text.muted | --color-text-muted | text-text-muted | #667080 | #AEB6C2 | |
color.text.inverse | --color-text-inverse | text-text-inverse | #FFFFFF | #0B1326 | |
color.text.on-action | --color-text-on-action | text-text-on-action | #FFFFFF | #FFFFFF | |
color.text.link | --color-text-link | text-text-link | #274A74 | #9FB8D6 | |
color.brand.primary | --color-brand-primary | bg-brand-primary | #4A78AD | #4A78AD | |
color.action.primary | --color-action-primary | bg-action-primary | #274A74 | #274A74 | |
color.action.hover | --color-action-hover | bg-action-hover | #1A3556 | #1A3556 | |
color.action.active | --color-action-active | bg-action-active | #10243D | #10243D | |
color.brand.subtle | --color-brand-subtle | bg-brand-subtle | #E3ECF5 | #1A3556 | |
color.brand.on-subtle | --color-brand-on-subtle | text-brand-on-subtle | #1A3556 | #C5D6E8 | |
color.accent.default | --color-accent-default | bg-accent-default | #3F9BB7 | #3F9BB7 | |
color.border.default | --color-border-default | border-border-default | #D2D8E2 | #667080 | |
color.border.strong | --color-border-strong | border-border-strong | #667080 | #AEB6C2 | |
color.border.focus | --color-border-focus | border-border-focus | #355F92 | #6F96C2 | |
color.status.success.bg | --color-status-success-bg | bg-status-success-bg | #F5F9F7 | #F5F9F7 | |
color.status.success.fg | --color-status-success-fg | text-status-success-fg | #0E7D36 | #0E7D36 | |
color.status.warning.bg | --color-status-warning-bg | bg-status-warning-bg | #FAF8F5 | #FAF8F5 | |
color.status.warning.fg | --color-status-warning-fg | text-status-warning-fg | #966106 | #966106 | |
color.status.danger.bg | --color-status-danger-bg | bg-status-danger-bg | #FAF5F5 | #FAF5F5 | |
color.status.danger.fg | --color-status-danger-fg | text-status-danger-fg | #D81212 | #D81212 | |
color.status.danger.surface | --color-status-danger-surface | bg-status-danger-surface | #D81212 | #D81212 | |
color.status.danger.on-surface | --color-status-danger-on-surface | text-status-danger-on-surface | #FFFFFF | #FFFFFF | |
color.status.danger.hover | --color-status-danger-hover | bg-status-danger-hover | #AD0E0E | #AD0E0E | |
color.status.danger.text | --color-status-danger-text | text-status-danger-text | #D81212 | #F15656 | |
color.status.info.bg | --color-status-info-bg | bg-status-info-bg | #F2F5F8 | #F2F5F8 | |
color.status.info.fg | --color-status-info-fg | text-status-info-fg | #1E62AE | #1E62AE |
Marka × tryb
Dział zatytułowany „Marka × tryb”data-brand × data-theme na <html> daje 10 kombinacji tych samych nazw.
Tryb ciemny to przemapowanie ról, nie druga paleta, więc komponent napisany raz
działa w obu. Podgląd na żywo w wybranej marce i trybie: strony /preview/*
mają własny przełącznik.
color.background.pagecolor.background.surfacecolor.background.inversecolor.text.primarycolor.text.secondarycolor.text.mutedcolor.text.on-actioncolor.brand.primarycolor.action.primarycolor.action.hovercolor.action.activecolor.brand.subtlecolor.accent.defaultcolor.border.defaultcolor.border.strongcolor.status.success.fgcolor.status.warning.fgcolor.status.danger.fgcolor.status.info.fgCo sprawdza bramka kontrastu
Dział zatytułowany „Co sprawdza bramka kontrastu”tokens/src/artifact.ts liczy WCAG 2.2 AA dla listy par, które komponenty
naprawdę renderują: 4,5:1 dla tekstu, 3:1 dla obrysu i pierścienia focus. Każda
para z listy jest liczona w każdej z 10 kombinacji. To nie jest iloczyn
kartezjański ról. Zestawienie spoza listy nie jest sprawdzone i nie wolno
zakładać, że przechodzi.
color.border.default jest poza bramką celowo: to dekoracyjny separator,
a nie granica komponentu w rozumieniu WCAG 1.4.11. Granicę pola, karty
i przycisku secondary niesie color.border.strong, liczony na background.page
i background.surface.
Nową parę tekst/tło dopisujesz do listy, zanim komponent jej użyje. Tak powstały
color.status.danger.text (kolor błędu renderowany wprost na tle strony)
i color.brand.on-subtle (tekst na brand.subtle): odcień, który wystarczał
jako plama, nie wystarczał jako tekst. Gdy odcień nie przechodzi progu, poprawka
wchodzi w paletę marki — źródło w repo: packages/design/tokens/src/brands/insurance.tokens.json,
nigdy w próg bramki.
Role są projektowane w parach. color.text.on-action idzie na color.action.*,
color.status.danger.on-surface na color.status.danger.surface. Zestawienie
ról z różnych rodzin to decyzja do tokenów, nie do lokalnego CSS.
Zakres kontroli dostępności w całym obszarze: standard dostępności.
Akcent jest kolorem towarzyszącym, nie drugą marką. Ma jedną rolę
i jedną wartość na markę, tę samą w obu trybach: color.accent.default.
Insurance ma w niej turkus, pozostałe cztery marki mają własny kolor znaku.
Akcent wolno obciążyć informacją: bramka mierzy go na background.page
i background.surface w progu 3:1 dla elementu nietekstowego (WCAG 2.2 SC 1.4.11),
w każdej z 10 kombinacji marki i trybu. Żeby próg wszędzie przechodził, dwie
wartości przyciemniono 2026-09-27 — akcent health i turkus insurance.
Powód i zakres zmiany (razem z plikami znaku) stoją
w paczce znaku — źródło w repo: packages/design/brand/README.md.
Próg mierzy bramka na obu podłożach stosu teł: background.page
i background.surface. Karta stoi na surface, więc pomiar karty jest tym samym
pomiarem; najciaśniejszy wynik to 3,02:1 (health, tryb jasny, na
background.page). Nowa powierzchnia w tokenach oznacza nowy pomiar akcentu
i nową parę w bramce, zanim obciąży się go informacją.
Granicy kontrolki akcentem nie rysujemy: od niej jest color.border.strong.
Plama, ilustracja, wykres, wyróżnienie — tak; obrys pola i pierścień focus — nie.
Akcent nie jest kolorem tekstu. Nie zastępuje przycisku głównego
(color.action.primary) ani linku (color.text.link). Kolorowy tekst na
akcencie wymaga najpierw własnego tokenu i wpisu do bramki, wzorem
brand.on-subtle.
Statusy
Dział zatytułowany „Statusy”success, warning, danger i info nie zależą od marki: status nie jest
kolorem marki, więc w pięciu portalach wartość jest ta sama. Każdy niesie parę
fg/bg przechodzącą 4,5:1, a danger dodatkowo cztery role dla wypełnienia
i dla tekstu wprost na tle — surface, on-surface, hover i text.
Od trybu zależy dokładnie jedna z tych ról: status.danger.text, czyli napis
błędu i obrys nieprawidłowej kontrolki wprost na tle. W trybie jasnym niesie
#D81212, w ciemnym #F15656. To nie wyjątek od reguły, tylko jej skutek:
wartość statusu wynika z wymaganego kontrastu, a stała #D81212 daje na
powierzchni trybu ciemnego 2,99:1 — poniżej 3:1 wymaganych dla granicy
kontrolki (WCAG 2.2 SC 1.4.11). Pozostałe role statusów, razem z danger.fg,
danger.surface, danger.on-surface i danger.hover, są identyczne we
wszystkich dziesięciu kombinacjach.
Koloru statusu nie dobierasz ręcznie i nie rozjaśniasz „żeby ładniej wyglądał”: wartość wynika z wymaganego kontrastu.
Barwa z dostawy. Obok pary fg/bg paleta niesie source — nasyconą barwę
z projektu, czytelną jako plama i jako ikona. Stoi zawsze obok etykiety
i tekstu, jako ich wzmocnienie, nigdy jako jedyny nośnik stanu, więc nie
podlega progowi elementu nietekstowego (WCAG 2.2 SC 1.4.11): stan nazywa napis,
barwa tylko prowadzi do niego wzrok. Warunek jest twardy. Status info barwy
source nie ma: projekt źródłowy jej nie podaje. Gdy plama ma stanąć
sama — bez etykiety, bez tekstu, jako jedyny znak stanu — próg 3:1 zaczyna
ją obowiązywać i wartość trzeba wtedy przyciemnić, tak jak przyciemniony jest
fg. Napisu source nie niesie: kolorem tekstu statusu jest fg.
Granice
Dział zatytułowany „Granice”- Kolor nie niesie informacji sam. Status ma etykietę i tekst, nie tylko barwę.
- Nie dobieramy odcienia w CSS komponentu. Brakująca rola to zmiana w tokenach.
- Tryb ciemny nie jest osobnym wariantem komponentu. Komponent czyta te same role.
- Marka nie rozgałęzia kodu. Różnicę niesie wartość tokenu, nie warunek w JSX.
Weryfikacja
Dział zatytułowany „Weryfikacja”pnpm -F @polisoteka/design-tokens build && pnpm -F @polisoteka/design-tokens check:tokenskończy się zielono. Tabele na tej stronie czytajądist/tokens.json, więc po przebudowaniu pokazują nową wartość bez ręcznej poprawki.- Każde nowe zestawienie tekst/tło ma wpis w liście par bramki.
- Komponent przełącza markę i tryb bez zmiany kodu. Sprawdzasz to na
/preview/*, w obu trybach. pnpm design:auditprzechodzi, łącznie z kontrolą axe i wartościami policzonymi w przeglądarce w 10 kombinacjach.
Tokeny i zależności,
Język wizualny marki,
Standard dostępności,
README paczki tokenów — źródło w repo: packages/design/tokens/README.md,
bramka kontrastu — źródło w repo: packages/design/tokens/src/artifact.ts.