Typografia
Techniczny: StableAkceptacja: ApprovedJeden krój tekstowy i trzynaście poziomów skali obsługują całą markę: ekrany,
dokumenty i materiały. Poziom wybierasz gotową klasą text-<rola>, która niesie
rozmiar, interlinię i wagę naraz.
Krojem marki jest Geist. W CSS rodzina nazywa się Geist Sans, bo pod taką
nazwą rejestruje ją @fontsource/geist-sans, z którego serwujemy webfont
lokalnie, bez CDN. Plik fontu niesie komplet polskich znaków diakrytycznych,
więc nie potrzebujemy osobnego subsetu.
Kroje dostarcza paczka ui plikiem @polisoteka/design-ui/styles/fonts.css.
Importuje go globals.css paczki, więc konsument dostaje kroje razem z resztą
stylów. Styleguide importuje ten sam plik, nie własną listę wag.
"Geist Sans", system-ui, -apple-system, "Segoe UI", sans-serif.doc { font-family: var(--font-family-sans);}Dalsza część stosu to kroje systemowe. Przejmują tekst, zanim webfont się wczyta, i wtedy, gdy się nie wczyta wcale. Serwujemy trzy wagi i tylko tych trzech używa skala: 400 w tekście i podpisach, 500 w etykietach, 600 w nagłówkach. Waga spoza tej trójki daje pogrubienie syntetyczne, nie odmianę kroju.
Pogrubienie w treści (strong, b, dt, th) bierze 600 z tokenu
font.weight.strong. Bez tej reguły przeglądarka dawała mu 700, którego nie
serwujemy. Element z klasą text-<rola> bierze wagę z poziomu, nie z tej reguły.
Kod, identyfikatory i wartości techniczne biorą var(--font-family-mono), czyli
Geist Mono z tej samej rodziny. Serwujemy dwie wagi: 400 w tekście i 600,
bo kod stoi też w pogrubieniu i w nagłówkach. W styleguide ten sam stos niesie
kod w treści i bloki kodu.
"Geist Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospaceSkala ma trzynaście poziomów. Cztery największe (display-l, display,
heading-l, heading-m) mają własny rozmiar i interlinię na mobile; pozostałe
dziewięć jest identyczne na obu szerokościach. Podgląd renderuje tę samą klasę,
którą dostaje kod.
| Podgląd | Rola | Klasa | Desktop (size / line-height / weight) | Mobile |
|---|---|---|---|---|
| Polisoteka® | display-l | text-display-l | 4rem / 1.125 / 600 | 3rem / 1.1667 / 600 |
| Polisoteka® | display | text-display | 3.5rem / 1.2143 / 600 | 2.5rem / 1.2 / 600 |
| Polisoteka® | heading-l | text-heading-l | 2.5rem / 1.2 / 600 | 2rem / 1.25 / 600 |
| Polisoteka® | heading-m | text-heading-m | 2rem / 1.25 / 600 | 1.75rem / 1.2857 / 600 |
| Polisoteka® | heading-s | text-heading-s | 1.5rem / 1.3333 / 600 | = desktop |
| Polisoteka® | heading-xs | text-heading-xs | 1.25rem / 1.4 / 600 | = desktop |
| Polisoteka® | body-l | text-body-l | 1.125rem / 1.6667 / 400 | = desktop |
| Polisoteka® | body-m | text-body-m | 1rem / 1.625 / 400 | = desktop |
| Polisoteka® | body-s | text-body-s | 0.875rem / 1.5714 / 400 | = desktop |
| Polisoteka® | label-l | text-label-l | 1.125rem / 1.5556 / 500 | = desktop |
| Polisoteka® | label | text-label | 1rem / 1.5 / 500 | = desktop |
| Polisoteka® | label-s | text-label-s | 0.875rem / 1.4286 / 500 | = desktop |
| Polisoteka® | caption | text-caption | 0.75rem / 1.3333 / 400 | = desktop |
Dwanaście poziomów przychodzi z arkusza grafika, z nazwami 1:1. Trzynasty,
label-l, dopisaliśmy decyzją Human: arkusz niesie etykietę w dwóch stopniach
(16 i 14 px), a duży przycisk potrzebuje trzeciego. Rozmiar jest ten sam co
w body-l, bo tyle napis dużego przycisku miał od początku; różni je waga,
500 zamiast 400. Interlinia wynika z logiki rodziny: pudełko wiersza stoi na
siatce 4 px i rośnie o 4 px na stopień, więc po 20 i 24 px idzie 28 px.
Poziom jest kompozytem
Dział zatytułowany „Poziom jest kompozytem”<h1 className="text-heading-l text-text-primary">Ubezpieczenie OC dla programisty</h1><p className="text-body-m text-text-secondary">Co obejmuje i ile kosztuje.</p>// rozmiar z jednego poziomu, waga z innego: to już nie jest poziom skali<p className="text-body-m font-semibold">…</p>Rozmiar, interlinia i waga są w dist/css/tokens.css osobnymi zmiennymi, bo
build nie emituje kompozytu jako jednej wartości. Klasa text-<rola> scala je
z powrotem i jest jedynym miejscem, gdzie ta trójka się spotyka. Potrzeba
mocniejszego akcentu w tekście oznacza inny poziom, nie inną wagę na tym samym.
Jedynym wyjątkiem jest wyróżnienie słowa w zdaniu: zapisujesz je elementem
<strong>, który bierze font.weight.strong, a nie klasą.
Kontrprzykładu nie da się już skompilować. dist/theme.css gasi domyślny motyw
Tailwinda także w przestrzeniach typografii — --font-weight-*, --text-*,
--leading-* i --tracking-* — więc font-semibold, font-medium,
text-sm i tracking-tight nie mają żadnej wartości do wzięcia i Tailwind nie
generuje dla nich klasy. Waga fontu przychodzi z poziomu skali. Token
font.weight.strong też nie daje klasy: font-strong się nie kompiluje,
a pilnuje tego bramka tailwind-namespaces.
Kiedy którego poziomu
Dział zatytułowany „Kiedy którego poziomu”| Poziom | Gdzie |
|---|---|
display-l, display | Hero startu B2B i LP zawodu. Jeden na stronę, dla nagłówka, który niesie zawód i zakres. |
heading-l | Tytuł strony albo artykułu. Jeden na stronę. |
heading-m, heading-s, heading-xs | Hierarchia sekcji, po kolei, bez przeskoków. Karta decyzji: nazwa produktu na heading-s, nagłówki kolumn porównania na heading-xs. |
body-l | Lead artykułu i LP. Esencja wyciągu dla zarządu, czyli to, co czyta osoba, która nie otworzy rozwinięcia. |
body-m | Domyślny tekst treści i interfejsu. Rozwinięcie wyciągu, uzasadnienie propozycji w karcie decyzji. |
body-s | Tekst pomocniczy: opisy pól, przypisy, wskazanie dokumentu źródłowego przy twierdzeniu o produkcie. |
label-l, label, label-s | Etykiety pól, przycisków i kontrolek; label-l w dużym przycisku. Waga 500 odróżnia je od body-* przy tym samym rozmiarze. |
caption | Metadane, znaczniki czasu, mikrocopy statusu, etykiety plakietek. |
Teksty, które wchodzą w te poziomy, mają własne strony: mikrocopy dla stanów i etykiet, komunikaty dla nagłówków i claimu.
Konteksty
Dział zatytułowany „Konteksty”| Klient | Przedsiębiorca | Pracodawca | Klient indywidualny |
|---|---|---|---|
| Gdzie czyta | LP zawodu i ekran sprawy | prezentacja i dokument dla zarządu, materiały dla HR | kalkulator i ścieżka zakupu |
| Co dominuje | body-m w gęstym rytmie, heading-s i heading-xs porządkują ofertę | body-l w esencji, body-m w rozwinięciu | label i body-s przy polach, caption przy zastrzeżeniach |
| Czego pilnujemy | różnica między label a body-m pokazuje, co jest etykietą, a co wartością | esencja i rozwinięcie mają różny poziom, nie ten sam rozmiar pogrubiony | cena i warunek jej ważności nie schodzą do caption |
Granice
Dział zatytułowany „Granice”- Nie dodajemy poziomu dla pojedynczego napisu. Trzynaście poziomów to cała skala.
- Nie zmieniamy rozmiaru, interlinii ani wagi osobno w miejscu użycia. Wyróżnienie
w zdaniu to
<strong>, nie klasa wagi. - Krój monospace zostaje przy kodzie i wartościach technicznych. Nie jest krojem ozdobnym.
- Próg desktopu niesie token
breakpoint.md. Cztery największe poziomy przełącza@variant md, więc zmiana progu to jedna wartość: siatka i responsywność.
Weryfikacja
Dział zatytułowany „Weryfikacja”- Poziom renderuje się klasą
text-<rola>, a nie zestawem własnych deklaracji. Sprawdzasz to wyszukaniemfont-sizeifont-weightw kodzie poza tokenami. - Cztery największe poziomy zmieniają rozmiar na progu
48rem. Oglądasz tę samą stronę poniżej i powyżej progu. - Długi nagłówek z polskimi znakami łamie się bez przycięcia i bez utraty diakrytyków, w jasnym i ciemnym trybie.
pnpm design:checkprzechodzi, łącznie z kontrolą literałów w zbudowanym styleguide.pnpm design:auditprzechodzi baterię krojów (audit/fonts.spec.ts): każda serwowana twarz Geist Sans i Geist Mono wczytuje się z polskimi znakami, kod stoi na Geist Mono, a tekst ma wyłącznie serwowane wagi.
Tokeny i zależności,
Siatka i responsywność,
Dokumenty, oferta i prezentacja,
Standard dostępności,
skala w prymitywach — źródło w repo: packages/design/tokens/src/core.tokens.json,
generator klas skali — źródło w repo: packages/design/tokens/src/build.ts,
dostawa krojów — źródło w repo: packages/design/ui/src/styles/fonts.css.