Przejdź do głównej zawartości

Odstępy i rytm

Techniczny: DraftAkceptacja: Approved

Odstępy mają jedną skalę i kierunkowe nazwy. Komponent nie wybiera liczby pikseli, tylko rolę: odstęp w kolumnie, odstęp w rzędzie, margines strony albo przerwę między sekcjami. Dzięki temu LP zawodu, karta decyzji i formularz Analizy Potrzeb Klienta mają ten sam rytm.

Skala bazowa żyje w core.space.* i nie jest publicznym kontraktem: ui/ i strony nigdy jej nie wołają. Kroki rosną coraz większymi przyrostami (0, 2, 4, 8, 12, 16, 24, 32, 48, 64 i 96 px), więc różnicę między sąsiednimi odstępami widać gołym okiem. Warstwa semantyczna wystawia z tej skali cztery role:

  • space.stack-* (xs–xl) to odstęp pionowy: gap w kolumnie, padding góra i dół.
  • space.inline-* (xs–lg) to odstęp poziomy: gap w rzędzie, padding lewo i prawo, przerwa między ikoną a etykietą.
  • space.page-gutter trzyma treść przy krawędzi okna.
  • space.section-gap rozdziela główne sekcje strony.

Nazwy są kierunkowe, nie rozmiarowe. Pytanie brzmi „to odstęp w kolumnie czy w rzędzie”, nigdy „sm czy md”.

<div className="flex flex-col gap-stack-md">
<div className="flex items-center gap-inline-sm">…</div>
</div>
.content-block {
padding: var(--spacing-stack-lg) var(--spacing-inline-md);
}

Każda rola jest zarejestrowana w namespace --spacing-* Tailwind v4, więc działa z dowolnym prefiksem odstępu: p-stack-md, gap-inline-sm, px-inline-lg, mt-stack-xl. Tabele niżej pokazują jedną reprezentatywną klasę na rolę; pozostałe prefiksy działają identycznie.

TokenZmienna CSSWartośćPodglądKlasa TailwindPrzeznaczenie
space.stack-xl--spacing-stack-xl32pxgap-stack-xlOddzielenie dwóch bloków treści w tej samej sekcji.
space.stack-lg--spacing-stack-lg24pxgap-stack-lgOdstęp między blokami wewnątrz karty albo panelu.
space.stack-md--spacing-stack-md16pxgap-stack-mdDomyślny odstęp między elementami w kolumnie; padding karty.
space.stack-sm--spacing-stack-sm8pxgap-stack-smOdstęp wewnątrz drobnego elementu; padding pionowy dużego przycisku.
space.stack-xs--spacing-stack-xs4pxgap-stack-xsEtykieta przy swoim polu, ikona przy tekście w jednej kolumnie; padding pionowy pola, przycisku i plakietki.
TokenZmienna CSSWartośćPodglądKlasa TailwindPrzeznaczenie
space.inline-lg--spacing-inline-lg16pxgap-inline-lgOdstęp między grupami kontrolek; padding boczny dużego przycisku.
space.inline-md--spacing-inline-md12pxgap-inline-mdDomyślny odstęp poziomy w rzędzie; padding boczny przycisku.
space.inline-sm--spacing-inline-sm8pxgap-inline-smKrótkie elementy w rzędzie: znaczniki, przyciski obok siebie; padding boczny pola, małego przycisku i plakietki.
space.inline-xs--spacing-inline-xs4pxgap-inline-xsIkona przy etykiecie wewnątrz przycisku.
TokenZmienna CSSWartośćPodglądKlasa TailwindPrzeznaczenie
space.page-gutter--spacing-page-gutter16pxpx-page-gutterMargines strony przy krawędzi okna.
TokenZmienna CSSWartośćPodglądKlasa TailwindPrzeznaczenie
space.section-gap--spacing-section-gap48pxgap-section-gapPrzerwa między głównymi sekcjami strony.

LP zawodu. Sekcje rozdziela section-gap, krawędzie strony trzyma page-gutter, a wewnątrz sekcji nagłówek odsuwa się od treści na stack-lg. Rytm jest ten sam dla każdego zawodu, bo strony powstają z jednego szablonu: różni się treść, nie odstępy. Szablon opisuje LP zawodu.

Karta decyzji. Karta stoi na komponencie Card: p-stack-md dookoła, gap-stack-md między blokami, gap-stack-xs w nagłówku. Powód propozycji, koszt i ograniczenie mają być widoczne bez rozwijania, więc kiedy karta rośnie, skracamy treść, a nie odstępy. Zawartość karty opisują dowody i rekomendacja.

Formularz Analizy Potrzeb Klienta. Etykieta trzyma się swojego pola na stack-xs, pola w grupie odsuwa stack-md, grupy pytań stack-lg, a przyciski pod formularzem stoją w rzędzie na inline-sm. Komunikat walidacji mieści się w odstępie pola, którego dotyczy, i nie tworzy zbiorczej ramki nad formularzem (mikrocopy).

// Tak: rola zgodna z kierunkiem układu, żadnej wartości wpisanej z ręki
<section className="flex flex-col gap-stack-lg px-page-gutter">
<div className="flex flex-col gap-stack-xs">
<Label htmlFor="nip">NIP</Label>
<Input id="nip" name="nip" />
</div>
<div className="flex items-center gap-inline-sm">
<Button type="submit">Przejdź do Analizy Potrzeb Klienta</Button>
<Button variant="secondary">Zmień dane</Button>
</div>
</section>
// Nie
<section className="flex flex-col gap-4 px-[18px]">
<div className="flex flex-col gap-inline-sm">…</div>
<div style={{ marginTop: "13px" }}>…</div>
</section>

Cztery błędy w kontrprzykładzie: gap-4 i px-[18px] biorą liczbę spoza skali, gap-inline-sm ustawia rolę poziomą w kolumnie, a marginTop naprawia gęstość w jednym miejscu zamiast poprawić rolę. Dwa z nich zatrzymuje automat: gap-4 nie kompiluje się wcale (dist/theme.css gasi domyślną skalę odstępów Tailwinda, więc klasa nie ma skąd wziąć wartości), a literał w atrybucie style jest błędem dla bramki no-hardcoded. Zostają dwa: wartość arbitralna px-[18px] i rola użyta w niewłaściwej osi — te wyłapuje review.

  • Skala nie ma wartości pośrednich. Brakujący odstęp to nowa rola w semantic.tokens.json, nie literał w komponencie.
  • stack-xs i inline-xs wskazują dziś ten sam krok skali, tak samo stack-sm i inline-sm. Rolę i tak wybieramy po kierunku: od md wartości się rozchodzą i pomyłka staje się widoczna.
  • page-gutter ma jedną wartość na każdej szerokości ekranu. Szerokości kontenerów, przełamania i siatkę opisuje Siatka i responsywność.
  • Odstęp nie zastępuje hierarchii nagłówków ani kolejności w kodzie. Czytnik ekranu nie widzi przerwy.

Walidator no-hardcoded w pnpm design:check zgłasza literał px i rem jako błąd, ale mierzy własny CSS i atrybuty style, nie klasy narzędziowe. Skalę domyślną Tailwinda zatrzymuje inny mechanizm: dist/theme.css gasi ją linią --spacing-*: initial, więc gap-4 nie kompiluje się i odstęp spoza naszej skali nie ma jak zadziałać. Wartość arbitralna w nawiasie — px-[18px] — przechodzi dalej, bo nie czyta żadnej skali. Ten jeden zapis wyłapuje review, nie automat.

Przed oddaniem sprawdź układ na wąskim ekranie i przy powiększeniu tekstu do 200%. Odstępy nie zależą od marki ani trybu, ale długość tekstu już tak, więc rząd kontrolek na inline-sm łamie się pierwszy. Porównanie zrzutów: pnpm design:audit.

Tokeny i zależności, Siatka i responsywność, Card, wybory, walidacja i postęp, mikrocopy, dowody i rekomendacja.