Układy CSS i elastyczne projektowanie stron

  1. 1 Informacje podstawowe
    1. Który tryb układu wybrać?
    2. Zasady pracy
  2. 2 Flexbox
  3. 3 Siatka i podsiatka
    1. Przykładowy kod: siatka i siatka podrzędna
  4. 4 Zapytania o kontenery
    1. Przykładowy kod: płynna typografia z użyciem jednostek zapytania o kontener
  5. 5 Natywne nakładki, pozycjonowanie kotwic i konteksty stosu
  6. 6 Śledzenie przepełnienia i stabilność układu
  7. 7 Mechanika widocznego obszaru i dystrybucja ścieżek
  8. 8 Siatka (tzw. mur)

1 Fundamentals

Jeśli to możliwe, korzystaj z silnika układu przeglądarki, aby zwiększyć wydajność. Zanim sięgniesz po zakodowane na stałe wymiary lub skomplikowane zapytania o media, wypróbuj rozmiary wewnętrzne, właściwości logiczne i aspect-ratio.

1.1 Który tryb układu wybrać?

Przejdź przez drzewo decyzyjne od góry do dołu i zatrzymaj się na pierwszym dopasowaniu. Pamiętaj, że układy mogą być zagnieżdżone w sobie, a każda decyzja jest podejmowana na podstawie przypadku użycia danego kontenera.

  1. Czy jest to prosty wiersz LUB kolumna elementów? Użyj flexbox – 1D, najpierw treść, treść jest rozprowadzana wzdłuż jednej osi.
  2. Czy element zagnieżdżony musi być wyrównany ze ścieżkami siatki elementu nadrzędnego wyższego poziomu? Użyj subgrid – 2D, relacja na pierwszym miejscu, dziedziczy ścieżki nadrzędne, dzięki czemu elementy podrzędne mogą wyrównywać się względem elementów równorzędnych.
  3. Czy jest to złożona struktura strony lub komponentu z wierszami ORAZ kolumnami? Użyj siatki – 2D, najpierw układ, definiujesz szkielet, a treść go wypełnia.
  4. Czy treść to długi tekst, który powinien zostać podzielony na równe kolumny? Użyj wielu kolumn – układ jednowymiarowy, styl gazety.
  5. Czy produkty mają różne wysokości i muszą być ciasno zapakowane? Używaj grid z grid-auto-flow: dense już dziś. Po natywne układy masonry (tzw. „grid lanes”) sięgaj tylko wtedy, gdy są one dostępne w Twojej wersji Baseline (patrz §8).
  6. Czy element musi unosić się nad stroną i pozostawać przestrzennie powiązany z elementem aktywującym, nawet poza granicami DOM lub kontekstami stosu? Użyj pozycjonowania zakotwiczonego – anchor-name w przypadku elementu aktywującego, position-anchor w przypadku nakładki (patrz §5).

1.2 Zasady pracy

  • Używaj właściwości logicznych (inline-size, block-size, margin-inline, padding-block, inset-inline-start) do określania wymiarów i odstępów układu – pełne informacje znajdziesz w artykule CSS: Modern Architecture.
  • Zastosuj model mentalny „najpierw treść” lub „najpierw układ”: flexbox, gdy elementy dyktują przepływ, siatka, gdy najpierw definiujesz szkielet.
  • Użyj skrótów place-* (place-content, place-items, place-self), aby wyrównać elementy w obu osiach w jednej deklaracji.
  • Zanim użyjesz stałych wartości width/height, wypróbuj rozmiary wewnętrzne (min-content, max-content, fit-content()) i elastyczne ścieżki (fr, minmax()) – mniej zapytań o media, bardziej odporne układy.
  • NIE używaj overflow: hidden do maskowania problemów z układem – rozwiąż je u źródła. Na przykład niektóre zastąpione elementy, takie jak <canvas>, <img>, <svg>, <video>, <iframe>, domyślnie używają display: inline, co może powodować luki w niektórych układach. Zamiast przycinać odstęp za pomocą overflow, usuń go, stosując wartość display-outside (np. display: block, grid, flex).
  • Użyj aspect-ratio, aby zarezerwować miejsce na multimedia i zapobiec przesunięciu układu przed załadowaniem zasobów.
.sidebar       { inline-size: max-content; }    /* Size to longest unbreakable token. */
.main-content  { inline-size: fit-content; }    /* Grow to available space, no further. */
.media         { aspect-ratio: 16 / 9; inline-size: 100%; block-size: auto; }
body.centered  { display: grid; place-content: center; min-block-size: 100dvb; }

Informacje o calc-size() i rozmiarze wewnętrznym uwzględniającym ograniczenia znajdziesz w artykule Obliczanie z użyciem rozmiarów wewnętrznych.

2 Flexbox

Układ jednowymiarowy – elementy są rozmieszczone wzdłuż jednej osi głównej i wyrównane względem osi poprzecznej. Można go używać w paskach nawigacyjnych, paskach narzędzi, wierszach elementów i dowolnych układach z jednym wierszem lub jedną kolumną.

Zalecane czynności:

  • Ustal kontekst za pomocą display: flex i ustaw oś główną za pomocą flex-direction (domyślnie row).
  • Używaj flex-wrap: wrap, gdy istnieje możliwość przepełnienia – nowrap bez overflow: auto/hidden będzie się rozlewać w wąskich widocznych obszarach.
  • Warstwa flex-wrap: balance po flex-wrap: wrap w zawijanych wierszach elementów kompaktowych (takich jak chmury tagów, pigułki filtrów, grupy przycisków lub linki nawigacyjne) umożliwia równomierne rozmieszczenie elementów w wierszach i zapobiega pozostawieniu pojedynczego elementu w ostatnim wierszu. Przeglądarki, które nie obsługują tej funkcji, korzystają z wrap.
  • Użyj skrótu flex <grow> <shrink> <basis> (np. flex: 1 1 250px) w przypadku elementów zamiast ustawiać osobno flex-grow/flex-shrink/flex-basis.
  • Do określania odstępów między elementami używaj gap (lub pełnej wersji row-gap/column-gap) zamiast marginesów elementów podrzędnych.
  • Dodaj prefiks wyrównania pozycji z safe (np. align-items: safe center), aby treść, którą można zaznaczyć, nie była przycinana, gdy kontener jest węższy niż jego zawartość.
  • Przesuń pojedynczy element na koniec głównej osi za pomocą margin-inline-start: auto (lub margin-block-start: auto) – to standardowe rozwiązanie.
  • Zastąp wyrównanie względem osi poprzecznej dla poszczególnych elementów za pomocą właściwości align-self.
  • Użyj align-items, aby wyśrodkować wszystkie elementy na osi poprzecznej. Użyj margin: auto na pojedynczym elemencie, aby wyśrodkować go niezależnie na obu osiach. Użyj align-content tylko wtedy, gdy kontener zawija się i ma dodatkowe miejsce między wierszami.
  • Ustaw min-inline-size: 0 (lub min-width: 0) w elementach flex, które zawierają długie, niepodzielne treści (adresy URL, kod, długie ciągi znaków) – elementy flex nie będą domyślnie zmniejszane poniżej rozmiaru zawartości, co powoduje przepełnienie.

Czego nie należy robić:

  • Nie używaj właściwości justify-self w przypadku elementów elastycznych – działa ona tylko w układach siatki, blokowych i pozycjonowanych bezwzględnie. Zamiast tego użyj marginesów automatycznych.
  • Nie używaj order ani flex-direction: *-reverse do zmiany kolejności treści interaktywnych. Zmieniają one tylko kolejność wizualną. Kolejność DOM nadal określa sekwencyjne zaznaczanie, więc przepływ kart klawiatury nie będzie odpowiadał temu, co widzi użytkownik.
  • Nie pomyl space-around (połowa odstępu na końcach) z space-evenly (równe odstępy przed, pomiędzy i po).
  • Pamiętaj o odwróceniu osi: gdy flex-direction: column, justify-content wyrównuje się na osi blokowej, a align-items wyrównuje się na osi liniowej – odwrotnie niż domyślnie.
  • Nie ustawiaj rozmiaru kontenera i jego elementów podrzędnych tak, aby wypełniały się nawzajem – jest to częsta przyczyna przepełnienia i nieoczekiwanych wyników. Określ rozmiar jednej strony.
  • Nie ustawiaj właściwości flex-basis i width/inline-size w tym samym elemencie – w kontekście flex właściwość flex-basis ma pierwszeństwo, a właściwość width jest ignorowana. Używaj flex-basis (lub skrótu flex) jako jedynego źródła wiarygodnych danych do określania rozmiaru elementów flex.
.card-grid        { display: flex; flex-flow: row wrap; gap: 1rem; }
.card-item        { flex: 1 1 250px; }                  /* grow, shrink, basis */
.card-item-action { margin-inline-start: auto; }        /* Push to main-axis end. */
.toolbar          { display: flex; align-items: safe center; }
.tag-list         { display: flex; flex-wrap: wrap; flex-wrap: balance; gap: 0.5rem; }

3 Siatka i podsiatka

Układ dwuwymiarowy – zdefiniuj wiersze ORAZ kolumny lub pozwól silnikowi je wygenerować. Siatka podrzędna umożliwia zagnieżdżonej siatce dziedziczenie ścieżek nadrzędnych, dzięki czemu elementy podrzędne są wyrównane względem elementów równorzędnych.

Wybór funkcji siatki:

  • Czy wiesz dokładnie, ile kolumn potrzebujesz?
    • Tak – używaj utworów z wulgaryzmami (grid-template-columns: 200px 1fr, repeat(3, 1fr) itd.)
    • Czy różne kolumny wymagają różnych rozmiarów (pasek boczny + główny, nagłówek obejmujący wszystkie)? → używaj grid-template-areas do oznaczania nazwanych, czytelnych regionów
    • Czy wszystkie kolumny są jednolite, czy też są pozycjonowane wyłącznie według numeru wiersza? → użyj repeat(N, ...) lub nazwanych linii
    • Nie (elastyczny, nieznana liczba elementów) – użyj repeat(auto-fit, minmax(min, 1fr))
    • Czy elementy w ostatnim wierszu powinny się rozciągać, aby wypełnić pozostałe miejsce? → auto-fit
    • Czy puste ścieżki w ostatnim wierszu powinny zachować minimalny rozmiar (zachowując puste miejsca w kolumnach)? → auto-fill
  • Czy musisz umieścić element w określonej lokalizacji?
    • Tak – użyj grid-column: <start> / <end> lub grid-area: <name>.
    • Nie (tylko obejmuje wiele ścieżek, pozycja przepływu nie ma znaczenia) – użyj grid-column: span <n>
  • Czy elementy podrzędne muszą dziedziczyć rozmiary ścieżek siatki nadrzędnej (wyrównanie do nieregularnych krawędzi między elementami równorzędnymi)?
    • Tak – użyj podsiatki na osi, której dotyczy problem.
    • Czy liczba dzieci na komórkę jest zmienna? → podsiatka tylko na jednej osi; na drugiej użyj grid-auto-rows/grid-auto-columns
    • Czy liczba dzieci jest stała? → podsiatka na obu osiach jest w porządku
    • No – standardowa siatka, nie jest potrzebna podsiatka.

Zalecane czynności:

  • Ustal kontekst za pomocą display: grid.
  • Używaj grid-template-areas w przypadku złożonych układów na poziomie strony – nazwy obszarów są zrozumiałe, a deklarację można wyrównać w wierszach i kolumnach, aby była czytelna na pierwszy rzut oka.
  • Użyj repeat(auto-fit, minmax(200px, 1fr)) w przypadku elastycznych siatek kart, które rozciągają wypełnione ścieżki, aby wypełnić wiersz, lub auto-fill, aby zachować puste powtarzające się ścieżki w ich minimalnym rozmiarze.
  • Użyj fr, aby uzyskać proporcjonalny rozkład ścieżek, i minmax(min, max), aby uzyskać elastyczne, ale ograniczone ścieżki.
  • Użyj grid-column: span <n>, aby określić rozmiar elementów na ścieżkach, grid-column: <start> / <end>, aby umieścić je w określonych wierszach, lub grid-area: <name>, aby umieścić je w nazwanych regionach.
  • Użyj podsiatki (grid-template-columns: subgrid lub grid-template-rows: subgrid), aby rozwiązać problem „nierównych krawędzi” na listach kart – elementy wewnętrzne, takie jak tytuły, metadane i wezwania do działania, będą wyrównane względem elementów równorzędnych.
  • Połącz deklarację subgrid z poprzedzającą ją jawną deklaracją grid-template-rows/-columns jako kreację zastępczą w tej samej kaskadzie dla starszych przeglądarek.

Czego nie należy robić:

  • Nie oczekuj, że rozmiar auto-fit/auto-fill będzie pochodzić z zawartości elementu – pochodzi on z argumentu rozmiaru repeat().
  • Nie używaj atrybutu grid-auto-flow: dense w treściach interaktywnych. Efektywnie pakuje elementy, ale zmienia ich kolejność wizualną, co zakłóca przepływ kart klawiatury w kolejności DOM.
  • Nie stosuj podsiatki do obu osi, gdy liczba elementów podrzędnych jest zmienna. Dodatki lądują na ostatniej ścieżce. Zamiast tego użyj grid-auto-rows/grid-auto-columns dla osi niejawnej.
  • Nie należy mylić justify-items/align-items (wyrównuje zawartość elementu w jego ścieżce) z justify-content/align-content (wyrównuje ścieżki siatki w kontenerze). Użycie niewłaściwego nie przyniesie żadnego efektu.
  • Nie używaj repeat(auto-fit/auto-fill, ...) bez określonego inline-size w kontenerze – wewnątrz display: inline-grid lub elementu flex bez rozmiaru kontener nie ma szerokości do podziału, co sprawia, że liczba ścieżek jest nieprzewidywalna.

3.1 Przykładowy kod: siatka i podsiatka

Powłoka strony: <main class="page-layout"> zawiera <header>, <aside>, <section class="card-grid"> z <div class="card"> elementami podrzędnymi i <footer>.

/* Align grid-template-areas in rows and columns for readability. */
.page-layout {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-template-areas:
    "header  header  header"
    "sidebar main    main"
    "footer  footer  footer";
  gap: 1.5rem;
}

header  { grid-area: header; }
aside   { grid-area: sidebar; }
footer  { grid-area: footer; }

.card-grid {
  grid-area: main;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  grid-template-rows: auto 1fr; /* title block, body block */
  gap: 1rem;
}

.card {
  grid-row: span 2;
  display: grid;
  /* Same-cascade fallback: ignored when subgrid is supported. */
  grid-template-rows: auto 1fr;
  grid-template-rows: subgrid;
}

4 Zapytania dotyczące kontenerów

Sprawdź rozmiar (lub obliczony styl) kontenera nadrzędnego, a nie widocznego obszaru. Model mentalny: zapytania o kontener = kontekst komponentu; zapytania o media = globalny układ strony i preferencje użytkownika (prefers-color-scheme, prefers-reduced-motion).

Zalecane czynności:

  • Ustaw kontekst kontenera za pomocą container-type: inline-size (zapytania dotyczące tylko szerokości) lub container-type: size (obie osie) w elemencie nadrzędnym, zanim będzie można wysyłać zapytania do elementów podrzędnych.
  • Nazwij kontenery za pomocą container-name (lub skrótu container: container: card / inline-size), gdy zagnieżdżone konteksty mogą kolidować.
  • Uwzględniaj jednostki zapytań o kontener podczas obliczania płynnego typu i odstępów: cqi/cqb (logiczne w wierszu/blokowe), cqw/cqh (fizyczne), cqmin/cqmax.
  • Jeśli używasz container-type: size, zawsze podawaj konkretną wartość block-size kontenera. Bez tego elementy podrzędne zostaną zwinięte, ponieważ ograniczenie rozmiaru wymusza na kontenerze ignorowanie zawartości.

Czego nie należy robić:

  • Nie używaj wartości block-size jako wartości container-type – jest ona nieprawidłowa. Użyj size dla obu osi.
  • Po zadeklarowaniu container-type nie oczekuj, że rozmiar wewnętrzny elementów podrzędnych będzie miał wpływ na kontener. Gdy kontener jest aktywny, jest obliczany tak, jakby nie miał elementów podrzędnych.
  • Nie polegaj na jednostkach zapytań o kontener w elementach podrzędnych niekwalifikującego się elementu nadrzędnego. W takim przypadku następuje powrót do małego widocznego obszaru (svw/svh).

4.1. Przykład kodu: płynna typografia z użyciem jednostek zapytania o kontener

.card-wrapper {
  container: card / inline-size; /* shorthand for container-name / container-type */
}

@container card (inline-size > 400px) {
  .content {
    display: flex;
    gap: 2rem;
  }
}

.title {
  /* Fluid type bound to the container width, not the viewport. */
  font-size: clamp(1rem, 4cqi, 2rem);
}

Wzorce stylów elastycznych opartych na komponentach znajdziesz w artykułach Stylizacja uwzględniająca rozmiar i Skalowanie płynne.

5 Natywne nakładki, pozycjonowanie zakotwiczone i konteksty stosu

Kiedy używać poszczególnych elementów podstawowych nakładki:

  • Używaj elementu popover w przypadku tymczasowego, niemodalnego interfejsu (menu wyskakujące, wyskakujące powiadomienia, etykietki) – znajduje się on w górnej warstwie i nie wymaga zarządzania elementem z-index.
  • Używaj <dialog> z .showModal() w przypadku interakcji modalnych, które wymagają blokowania zaznaczenia i nieaktywnego tła.
  • Nie łącz popover i .showModal() w tym samym elemencie – są to wzajemnie wykluczające się stany środowiska wykonawczego.

Pozycjonowanie kotwicy (układ przestrzenny nakładek):

  • Użyj position-area (lub anchor() w przypadku wstawek) i anchor-size(), aby określić położenie i rozmiar nakładki względem jej aktywatora.
  • Użyj position-try-fallbacks: flip-block (lub flip-inline), aby umożliwić przeglądarce zmianę pozycji, gdy nakładka przekracza widoczny obszar.
  • Nie mieszaj fizycznych i logicznych słów kluczowych w jednej wartości position-area – wybierz jeden układ współrzędnych.
  • Wykrywaj funkcje za pomocą @supports (anchor-name: --x) i zapewnij kreację zastępczą z pozycją bezwzględną.

Pełne informacje o implementacji, strategie polyfill i odniesienia do wartości popover znajdziesz w artykułach Declarative Dialog and Popover Control i Position Aware Tooltips. Informacje o pozycjonowaniu zakotwiczonym stosowanym w przypadku menu, wyskakujących okienek i wskaźników kart znajdziesz w artykułach Menu uwzględniające widoczny obszar i Podkreślenie karty pozycjonowania zakotwiczonego.

6 Śledzenie przepełnienia i stabilność układu

Zarządzaj przesunięciami układu, paskami przewijania i przycinaniem w przewidywalny sposób.

Zalecane czynności:

  • Użyj overflow: auto, aby paski przewijania pojawiały się tylko wtedy, gdy treść faktycznie się przelewa.
  • Użyj overflow: clip, aby przyciąć zawartość bez tworzenia kontenera przewijania. Aby włączyć przepełnienie, użyj overflow-clip-margin.
  • Użyj scrollbar-gutter: stable, aby zarezerwować miejsce na paski przewijania i zapobiec przesunięciom układu, gdy treści się powiększają.
  • Użyj overscroll-behavior: contain (lub none) w zagnieżdżonych obszarach przewijania (takich jak paski boczne, okna modalne i listy), aby zapobiec przenoszeniu łańcuchów przewijania na stronę, lub overscroll-behavior: chain w pośrednich kontenerach przewijania (takich jak ścieżki przesunięcia, aby odsłonić), gdy nadmierne przewijanie powinno być przenoszone do nadrzędnego elementu przewijania bez wywoływania lokalnego efektu gumki.
  • Użyj triady -webkit-line-clamp + display: -webkit-box + -webkit-box-orient: vertical do obcinania wielowierszowego – pomimo prefiksu ten wzorzec jest w pełni określony i nie jest wycofany. Zadeklaruj obok niego skrót line-clamp bez prefiksu. Przeglądarki, które jeszcze go nie obsługują, zignorują tę właściwość bez żadnych konsekwencji. Czego nie należy robić:

  • Nie używaj overflow: scroll, gdy wystarczy auto – scroll wymusza paski przewijania, nawet gdy nie ma czego przewijać.

  • Nie używaj właściwości overflow: hidden, gdy chcesz tylko przyciąć – hidden tworzy kontener przewijania, który można przewijać programowo.

.scrollable-list {
  max-block-size: 400px;
  overflow-y: auto;
  scrollbar-gutter: stable;       /* Reserve scrollbar space. */
  overscroll-behavior: contain;   /* No scroll chaining into the page. */
}

.snippet {
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  line-clamp: 3;                  /* Ignored where unsupported. */
  overflow: clip;
}

Szczegółowe informacje o overflow: clip i overflow-clip-margin znajdziesz w artykule Kontrola przycinania przepełnienia. Informacje o przekazywaniu zagnieżdżonego przewijania za pomocą overscroll-behavior: chain w interfejsie przesuwania w celu wyświetlenia znajdziesz w artykule Przesuwanie w celu wyświetlenia. Informacje o kolorze, rozmiarze i motywie paska przewijania znajdziesz w artykułach Dostosowywanie koloru lub grubości paska przewijania, Tryb ciemny i Dostosowywanie paska przewijania do preferencji dotyczących wysokiego kontrastu.

7 Mechanizmy widocznego obszaru i dystrybucja ścieżek

  • Używaj dvh/dvw w przypadku kontenerów układu mobilnego, które muszą uwzględniać zmiany interfejsu przeglądarki (zwijanie/rozwijanie paska adresu URL).
  • Nie używaj 100vw w przypadku układu o pełnej szerokości – ignoruje on szerokość paska przewijania i powoduje przepełnienie w poziomie. Zamiast niej użyj zasad 100%, 100dvw lub 100svw.

Informacje o punkcie wejścia elastycznego projektowania stron (dynamiczne jednostki widocznego obszaru, rezerwowanie miejsca na multimedia, płynna typografia) znajdziesz w artykule Elastyczne projektowanie stron.

8 Pasy siatki (tzw. mur)

Specyfikacja jest rozwijana pod nazwą „grid lanes” (np. display: grid-lanes), podczas gdy we wcześniejszych eksperymentalnych prototypach używano grid-template-rows: masonry.

Zalecane czynności:

  • Użyj siatki z właściwością grid-auto-flow: dense, aby uzyskać ciasne upakowanie, akceptując, że kolejność DOM może nie odpowiadać kolejności wizualnej.
  • Używaj układu wielokolumnowego (columns: 3; column-gap: 1rem) w przypadku treści o dużej objętości, gdy elementy są fragmentami dokumentu, a nie kartami o jednakowej wadze.
  • Traktuj grid-template-rows: masonry tylko jako progresywne ulepszanie – wykrywaj funkcje za pomocą @supports.

Czego nie należy robić:

  • Nie wprowadzaj grid-template-rows: masonry jako twardego wymagania, dopóki nie zostanie osiągnięty cel Baseline.
.gallery       { columns: 3 200px; column-gap: 1rem; }
.gallery > *   { break-inside: avoid; margin-block-end: 1rem; }

@supports (grid-template-rows: masonry) {
  .gallery {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
    grid-template-rows: masonry;
    gap: 1rem;
    columns: unset;
  }
}