Przejdź do głównej zawartości

Powierzchnie, obrysy i głębia

Techniczny: DraftAkceptacja: Draft

Powierzchnia to cztery decyzje naraz: tło, granica, promień i cień. System daje na nie cztery rodziny tokenów i jedną twardą regułę: granicę komponentu rysujemy na color.border.strong, nigdy na color.border.default. Reszta układa się z tego, co robią dziś Card, Input, Button i Badge.

PodglądRola (token)Zmienna CSSKlasa TailwindHex — lightHex — dark
color.background.page--color-background-pagebg-background-page#F8F9FB#0B1326
color.background.surface--color-background-surfacebg-background-surface#FFFFFF#1C2430
color.background.inverse--color-background-inversebg-background-inverse#0B1326#FFFFFF
  • background.page to tło strony.
  • background.surface to tło wszystkiego, co leży na stronie: karty, kontrolki (Input, Button secondary, Badge neutral) i elementu unoszącego się nad treścią.
  • background.inverse to sekcja odwrócona względem trybu, razem z color.text.inverse.

Powierzchnie są dwie, nie trzy. Osobnej roli na element uniesiony nie ma: background.surface niesie zarówno to, co leży na stronie, jak i to, co się nad nią unosi. Dwie nazwy na jedną barwę nie dają dwóch poziomów.

Uniesienie nie jest więc stopniem koloru. Niosą je cień w trybie jasnym i obrys w trybie ciemnym (niżej, „Cienie”). Karta nie opiera się na samym tle.

Warstwy nie mnożymy. Poziomy są dwa: treść leży na stronie albo unosi się nad nią. Kartę w karcie zastępuje zmiana tła na background.page wewnątrz kontenera, a nie druga karta z własnym cieniem (Card).

Stan :hover przycisku w wariancie secondary cofa tło o poziom, z surface na page. Zmiana w stosie teł niesie tu informację o stanie, nie dekorację.

Komponent rysuje granicę na color.border.strong. color.border.default zostaje separatorem dekoracyjnym; dziś używa go obrys Badge w wariancie neutral i linie w interfejsie tej dokumentacji.

PodglądRola (token)Zmienna CSSKlasa TailwindHex — lightHex — dark
color.border.default--color-border-defaultborder-border-default#D2D8E2#667080
color.border.strong--color-border-strongborder-border-strong#667080#AEB6C2
color.border.focus--color-border-focusborder-border-focus#355F92#6F96C2

Granica komponentu mówi, dokąd sięga kontrolka, więc podlega WCAG 2.2 SC 1.4.11 i potrzebuje kontrastu 3:1. border.default tego progu nie gwarantuje i stoi świadomie poza bramką kontrastu. border.strong mierzy bramka tokens/src/artifact.ts, i to na obu tłach, na których komponenty naprawdę stoją: background.page i background.surface, w każdej z dziesięciu kombinacji marki i trybu.

Wyjątek dla border.default obowiązuje tylko dopóki komponenty trzymają się tego podziału. Granica komponentu przeniesiona na ten token nie zapali bramki, bo bramka go nie mierzy, a kontrast i tak zniknie.

TokenZmienna CSSWartośćPodglądKlasa TailwindPrzeznaczenie
radius.full--radius-full9999pxrounded-fullPigułka: Badge.
radius.xl--radius-xl16pxrounded-xlBez konsumenta dzisiaj.
radius.lg--radius-lg12pxrounded-lgKontener treści: Card, panel.
radius.md--radius-md8pxrounded-mdKontrolka: Button, Input.
radius.sm--radius-sm4pxrounded-smDrobny element: próbka koloru, przycisk paska narzędzi.

Promień wynika z rangi elementu, nie z jego rozmiaru: kontrolka dostaje radius.md, kontener treści radius.lg, element w kształcie pigułki radius.full. Wartości nie zmieniają się z marką ani trybem, więc ten sam komponent ma identyczny kształt we wszystkich pięciu portalach.

TokenZmienna CSSWartośćPodglądKlasa TailwindPrzeznaczenie
shadow.lg--shadow-lg0 12px 32px rgba(16, 26, 46, 0.16)shadow-lgBez konsumenta dzisiaj; rola dla warstwy nad stroną.
shadow.md--shadow-md0 4px 12px rgba(16, 26, 46, 0.12)shadow-mdBez konsumenta dzisiaj; rola dla elementu nad treścią.
shadow.sm--shadow-sm0 1px 2px rgba(16, 26, 46, 0.08)shadow-smKarta leżąca na stronie (Card).

Cień dokłada się do stosu teł, nie zastępuje go. Karta leżąca na stronie bierze shadow.sm razem z tłem surface i obrysem border.strong. shadow.md i shadow.lg czekają na pierwszy element unoszący się nad treścią; w ui/ nie ma go dzisiaj.

W trybie ciemnym cień nie jest nośnikiem uniesienia. Wszystkie trzy cienie to ten sam półprzezroczysty granat (rgba(16, 26, 46, …)), czyli barwa bliska tłu strony: shadow.lg złożony ze swoim kryciem na background.page daje 1,01:1. Tego nie widać. W trybie jasnym cień pracuje i zostaje — ta sama warstwa na tle strony daje tam 1,39:1, czyli widoczną różnicę na jasnym podłożu.

W trybie ciemnym uniesienie niosą dwie rzeczy: różnica tła (karta wobec strony, navy.800 na navy.900 — 1,18:1) i obrys. Pracuje obrys. Granica Card stoi na border.strong i daje 7,64:1 wobec tła karty oraz 9,05:1 wobec tła strony; nawet dekoracyjny border.default daje tam 3,12:1, czyli więcej niż tło i cień razem. Dlatego element uniesiony w trybie ciemnym zawsze ma obrys, a cień jest w nim dekoracją. Cień nigdy nie jest jedynym nośnikiem granicy — w żadnym z dwóch trybów.

TokenZmienna CSSWartośćPodglądPrzeznaczenie
border-width.focus--border-width-focus2pxPierścień :focus-visible, grubość i odsunięcie.
border-width.hairline--border-width-hairline1pxObrys i separator we własnym CSS.

Te dwa tokeny służą własnemu CSS, na przykład globals.css paczki ui i stylom tej dokumentacji. Tailwind ich nie dostaje: theme.css rejestruje kolor, typografię, odstępy, promienie i cienie, ale nie grubość obrysu. Klasa border w komponencie niesie więc domyślną grubość Tailwinda, równą dziś wartości border-width.hairline.

Pierścień :focus-visible ustawia globals.css jeden raz dla całej paczki, z border-width.focus i focus.ring. Komponent go nie powtarza i nie wygasza. Wymagania dla fokusu opisuje standard dostępności.

// Tak: gotowy komponent niesie komplet decyzji o powierzchni
<Card>
<CardHeader>
<CardTitle as="h2">OC zawodowe</CardTitle>
<CardDescription>Zakres, cena i ograniczenie</CardDescription>
</CardHeader>
<CardContent>…</CardContent>
</Card>
// Nie
<article
className="border border-border-default bg-background-surface"
style={{ borderRadius: "10px", boxShadow: "0 2px 6px rgba(0, 0, 0, 0.2)" }}
>
…
</article>

Kontrprzykład psuje trzy rzeczy naraz: rysuje granicę komponentu na tokenie dekoracyjnym, wpisuje promień spoza skali i dokłada własny cień zamiast wybrać poziom z systemu. Kontrast granicy spadnie poniżej 3:1, a element przestanie wyglądać jak karta w pozostałych markach i trybach.

  • Nowy poziom uniesienia to zmiana w tokenach, nie lokalny box-shadow.
  • Nakładka pod modalem, przyklejony nagłówek i kolejność warstw nie mają dziś ani tokenu, ani komponentu. Nie zastępuj ich improwizacją w miejscu użycia.
  • Powierzchnia nie zmienia znaczenia stanu. Błąd, ostrzeżenie i sukces niosą role color.status.*, opisane w kolorze.

@todo(surfaces): wykonać poniższy brief, przedstawić rezultat i dowody odbioru. Usunięcie znacznika nie jest wykonaniem zadania.

  • Zakres: rozstrzygnąć cztery rzeczy, których dziś w systemie nie ma: (1) nakładkę i tło warstwy nad stroną, czyli modal i arkusz, razem z tokenem przyciemnienia tła i kolejnością warstw; (2) element unoszący się nad treścią bez pełnej nakładki, czyli dropdown i popover, jako pierwszego konsumenta shadow.md; (3) czytelność uniesienia w trybie ciemnym dla warstw NAD stroną — dla karty rozstrzyga ją obrys, dla nakładki, dropdownu i paska rozstrzygnięcia nie ma; (4) przyklejone paski, nagłówek i pasek akcji, wraz z granicą, która pojawia się przy przewijaniu. Rozstrzygnięcia zapadają na dwóch realnych ekranach: start B2B i decyzja zdrowia.
  • Konwencja: najpierw reguła lub gotowe zastosowanie, potem przykład i kontrprzykład, następnie granice oraz sposób sprawdzenia. Nie zastępuj materiału opisem planu jego przygotowania.
  • Źródła: tokens/src/semantic.tokens.json (opisy ról tła), tokens/README.md (decyzja 5: border.default poza bramką kontrastu, border.strong w niej), tokens/src/artifact.ts (pary mierzone bramką), komponenty Card, Input, Button, Badge.
  • Granica swobody: możesz redagować i pokazywać istniejące ustalenia. Nowy poziom uniesienia, nowy token i zmianę wyglądu komponentu przedstaw do decyzji; nie uzupełniaj z pamięci.

pnpm -F @polisoteka/design-tokens check:tokens mierzy border.strong na tłach page i surface w dziesięciu kombinacjach marki i trybu. Na surface mierzy też tekst: text.secondary, text.link i status.danger.text. border.default jest poza tą bramką świadomie, więc granicę komponentu postawioną na nim wyłapuje wyłącznie review.

Przed oddaniem obejrzyj kartę i kontrolkę w trybie ciemnym: obrys musi być widoczny bez pomocy cienia. Porównanie zrzutów i skan axe: pnpm design:audit.

Tokeny i zależności, kolor, odstępy i rytm, Card, Input, Button, Badge, standard dostępności.