Przejdź do głównej zawartości

Siatka i responsywność

Techniczny: DraftAkceptacja: Draft

Układ zaczyna się od najwęższego ekranu. Szerokość okna zmienia go na czterech progach, a szerokość treści ograniczają dwa kontenery. Jedno i drugie jest tokenem, więc w kodzie nie pada ani liczba pikseli, ani zapytanie o media napisane z ręki.

TokenZmienna CSSWartośćPodgląd
breakpoint.sm--breakpoint-sm40rem
breakpoint.md--breakpoint-md48rem
breakpoint.lg--breakpoint-lg64rem
breakpoint.xl--breakpoint-xl80rem

Tyle ich jest. Build wygasza skalę domyślną Tailwinda, więc wariant spoza tej czwórki, na przykład 2xl:, nie powstaje i klasa z nim nic nie zrobi. Nowy próg zaczyna się od tokenu, nie od klasy w komponencie.

Progi są te same w pięciu markach i dwóch trybach. Zapytania o media nie da się zakresować atrybutem na <html>, więc próg nadpisany dla jednej marki byłby martwy. Build zatrzymuje się, gdy taka różnica się pojawi.

Zapis zależy od miejsca. Wartość pod spodem jest jedna.

Komponent i strona
<section className="grid grid-cols-1 gap-stack-lg md:grid-cols-2">…</section>
Własny CSS przetwarzany przez Tailwind
.article-body {
font-size: var(--font-body-m-size);
@variant md {
font-size: var(--font-body-l-size);
}
}
Nie tak
/* @media nie rozwija var(). Ta reguła kompiluje się bez błędu, przechodzi
każdą bramkę i nie dopasowuje się nigdy. */
@media (min-width: var(--breakpoint-md)) { … }
/* Literał zadziała, ale jest drugą kopią progu: zmiana tokenu go nie ruszy. */
@media (min-width: 48rem) { … }

W bloku <style> komponentu Astro @variant nie zadziała, bo ten CSS nie widzi motywu Tailwinda. Tam przełamanie zapisujemy klasą z wariantem w znaczniku, razem z klasą bazową. Styl komponentu ma wyższą specyficzność i wygrałby z samym wariantem.

Deklaracja bez wariantu opisuje najwęższy ekran. Próg tylko dodaje: md: nadpisuje to, co już stoi, i nie występuje bez wersji bazowej. Dlatego w systemie nie ma progów liczonych od góry (max-width).

Na progu md zmieniają się dziś dwie rzeczy:

  • typografia — cztery największe poziomy (display-l, display, heading-l, heading-m) rosną, pozostałe dziewięć jest identycznych na każdej szerokości (Typografia);
  • układ artykułu — kolumna poboczna schodzi spod treści i staje obok niej (Artykuł i treść źródłowa).

Odstępy nie zmieniają się na żadnym progu. page-gutter i section-gap mają jedną wartość na każdej szerokości (Odstępy i rytm).

TokenZmienna CSSWartośćPodglądPrzeznaczenie
size.container-aside--size-container-aside16remKolumna poboczna obok treści: spis treści, panel kontekstu.
size.container-page--size-container-page64remGórny limit kolumny treści. Powyżej rośnie margines, nie tekst.

container-page ogranicza kolumnę treści i ma tę samą wartość co próg lg. Szersze okno nie rozciąga akapitu na całą szerokość, tylko dokłada margines. container-aside trzyma kolumnę poboczną. Obok treści staje dopiero od md, niżej idzie pod nią.

Tak
<main className="mx-auto w-full max-w-(--size-container-page) px-page-gutter">
<div className="grid grid-cols-1 gap-stack-lg md:grid-cols-[minmax(0,1fr)_var(--size-container-aside)]">
<article className="flex flex-col gap-stack-md">…</article>
<aside className="flex flex-col gap-stack-sm">…</aside>
</div>
</main>
Nie tak
<main className="mx-auto max-w-[1024px] px-4">
<div className="grid grid-cols-[1fr_256px] gap-6">…</div>
</main>

Cztery błędy w kontrprzykładzie. max-w-[1024px] i 256px wpisują szerokość z ręki zamiast wziąć kontener; wartość arbitralna w nawiasie nie czyta żadnej skali, więc zadziała i wyłapie ją review. px-4 i gap-6 sięgały po skalę własną Tailwinda obok naszej — dziś nie kompilują się wcale, bo dist/theme.css gasi domyślny motyw w tych przestrzeniach nazw. Kolumny stoją obok siebie od razu, bez wersji na wąski ekran, więc na telefonie poboczny panel ściska treść. Ostatni błąd jest cichy: 1fr ma domyślne minimum auto, nie zero, więc długie słowo albo tabela rozpycha kolumnę poza ekran. Dlatego w przykładzie wyżej stoi minmax(0, 1fr).

Kolumny deklarujemy przy elemencie, który ich potrzebuje. Wzór jest za każdym razem ten sam: grid, przerwa z roli odstępu i liczba kolumn zaczynająca się od jednej.

<ul className="grid grid-cols-1 gap-stack-md md:grid-cols-3">…</ul>

Nazwanej siatki o stałej liczbie kolumn system dziś nie ma i żadna strona jej nie zakłada. Liczbę kolumn wybiera treść: elementy o tej samej wadze dzielą się równo, treść z kolumną poboczną nie.

KlientPrzedsiębiorcaPracodawcaKlient indywidualny
Co niesie kolumna pobocznakontekst oferty przy treści: mapa potrzeb, wycena wstępna, powód propozycjinawigację po porównaniu pakietównic; kalkulator prowadzi jednym torem
Czego pilnujemy przy zwężaniugęsta strona oferty nie może zgubić ceny i zastrzeżenia pod złożonym panelemesencja wyciągu dla zarządu czyta się na telefonie w jednej kolumnie, bez poziomego przewijania tabelicena i warunek jej ważności zostają przy polu, którego dotyczą

Kolejność w dokumencie jest kolejnością czytania. Kolumna, która na wąskim ekranie schodzi pod treść, trafia tam w tej kolejności, w której stoi w kodzie. Czytnik ekranu i klawiatura nie widzą kolejności ustawionej wizualnie (standard dostępności).

  • Nowy próg i nowa szerokość kontenera powstają w warstwie semantycznej, nie jako literał w CSS ani wartość w nawiasie kwadratowym przy klasie.
  • Progi nie zależą od marki ani od trybu.
  • Próg nie naprawia treści. Tabela, która nie mieści się na telefonie, wymaga innej formy danych, a nie kolejnego przełamania.

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

  • Zakres: trzy rzeczy bez rozstrzygnięcia. (1) Szerokość czytania prozy na stronie bez kolumny pobocznej: dziś akapit dochodzi tam do container-page, bo własnej szerokości nie ma. (2) Szerokość formularza Analizy Potrzeb Klienta. (3) Czy system potrzebuje nazwanej siatki o stałej liczbie kolumn, czy wystarczy grid-cols-* przy elemencie. Rozstrzygnięcie to token w warstwie semantycznej plus wiersz w tabeli wyżej.
  • Źródła: platforma marki, realne strony: LP zawodu, karta decyzji, wyciąg dla zarządu, formularz Analizy Potrzeb Klienta. Wartość bierze się z pomiaru na tych stronach, nie z zalecenia ogólnego.
  • Granica swobody: progi sm/md/lg/xl i dwa kontenery są ustalone i ich nie ruszasz. Nową szerokość przedstawiasz do decyzji razem ze stroną, na której ją zmierzono.
  1. W kodzie nie ma @media napisanego ręcznie ani szerokości w nawiasie kwadratowym. Sprawdzasz to wyszukaniem @media i max-w-[ poza artefaktem budowania tokenów.
  2. Ta sama strona czyta się przy każdej szerokości od 320 px w górę, bez poziomego przewijania i bez treści uciętej przy krawędzi (WCAG 2.2 AA, reflow).
  3. Przy powiększeniu tekstu do 200% układ przechodzi na jedną kolumnę zamiast ściskać obie.
  4. Kolejność treści czytana klawiszem Tab zgadza się z kolejnością na ekranie, także po zejściu kolumny pobocznej pod treść.
  5. pnpm design:check przechodzi, łącznie z kontrolą literałów w zbudowanym styleguide; porównanie zrzutów: pnpm design:audit.

Tokeny i zależności, Typografia, Odstępy i rytm, Artykuł i treść źródłowa, Standard dostępności, progi i kontenery w prymitywach — źródło w repo: packages/design/tokens/src/core.tokens.json, mechanika progów w @theme — źródło w repo: packages/design/tokens/README.md.