Przed CSS Grid budowanie właściwego układu strony oznaczało elementy pływające, clearfiksy, ujemne marginesy i modlitwę, że kolumny się nie zawalą. Flexbox pomógł, ale jest fundamentalnie jednowymiarowy — masz wiersz lub kolumnę, nie oba jednocześnie. CSS Grid to pierwszy system układu natywny dla CSS, który jest naprawdę zaprojektowany dla układów dwuwymiarowych. Wyzwaniem nie jest obsługa przeglądarek — jest ona powszechna od 2017 roku. Wyzwaniem jest to, że API ma duży obszar powierzchni, a nazewnictwo jest na tyle niespójne, że może być mylące bez jasnego modelu bazowego. Ten artykuł skupia się na częściach, których będziesz używać w prawdziwej pracy.

Wiersze, kolumny i kontener siatki

Wszystko zaczyna się od display: grid na kontenerze. Po ustawieniu bezpośrednie dzieci automatycznie stają się elementami siatki — nie jest potrzebna żadna klasa. Kolumny i wiersze definiujesz za pomocą grid-template-columns i grid-template-rows.

Kluczową jednostką do zrozumienia jako pierwszą jest fr — jednostka ułamkowa. Reprezentuje ułamek dostępnej przestrzeni w kontenerze siatki po uwzględnieniu ścieżek o stałym rozmiarze. Jedna 1fr oznacza „weź jeden udział z pozostałej przestrzeni". Dwie kolumny 1fr 1fr oznaczają równe połowy. 2fr 1fr oznacza, że pierwsza kolumna dostaje dwa razy więcej niż druga. Ta jednostka sprawia, że płynne siatki czują się naturalne.

css
/* 3-column blog card layout */
.card-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-template-rows: auto;
  gap: 1.5rem;
}

/* repeat(3, 1fr) is shorthand for: 1fr 1fr 1fr */
/* auto rows size to their content */

repeat(n, track) to skrót, dzięki któremu nie musisz pisać tego samego rozmiaru ścieżki pięć razy. Działa również z mieszanymi wartościami: repeat(3, 1fr 2fr) tworzy 6-kolumnową siatkę z naprzemiennie wąskimi i szerokimi ścieżkami. W przypadku wierszy auto jest prawie zawsze tym, czego chcesz — wiersze dopasowują rozmiar do zawartości, chyba że wyraźnie potrzebujesz stałej wysokości.

html
<div class="card-grid">
  <article class="card">
    <img src="/posts/grid-post-1.jpg" alt="CSS Grid post 1">
    <h2>Getting Started with Grid</h2>
    <p>A practical intro for developers moving from Flexbox.</p>
  </article>
  <article class="card">
    <img src="/posts/grid-post-2.jpg" alt="CSS Grid post 2">
    <h2>Grid vs Flexbox</h2>
    <p>Which layout tool to reach for, and when.</p>
  </article>
  <article class="card">
    <img src="/posts/grid-post-3.jpg" alt="CSS Grid post 3">
    <h2>Responsive Grids Without Media Queries</h2>
    <p>Using minmax() to handle reflow automatically.</p>
  </article>
</div>

Właściwość gap

Przed Gridem dodawanie odstępów między dziećmi układu oznaczało marginesy — a marginesy oznaczały pamiętanie o resetowaniu ostatniego dziecka, radzenie sobie ze zwijaniem i dodawanie ujemnych marginesów na kontenerze, aby to skompensować. gap rozwiązuje to w czysty sposób. Dodaje przestrzeń tylko między ścieżkami, nigdy na zewnętrznych krawędziach siatki.

css
/* Uniform gap between all rows and columns */
.card-grid {
  gap: 1.5rem;
}

/* Different row and column gaps */
.dashboard {
  row-gap: 2rem;
  column-gap: 1rem;
}

/* gap is shorthand: row-gap then column-gap */
.dashboard {
  gap: 2rem 1rem;
}
Uwaga: gap działa również we Flexboksie (zastąpił stary alias grid-gap). Jeśli już używasz Flexboksa dla komponentu i potrzebujesz tylko odstępów między elementami, możesz użyć gap tam również bez przełączania się na Grid.

Jawne rozmieszczanie elementów

Domyślnie elementy siatki przepływają do następnej dostępnej komórki w kolejności źródłowej. Ale prawdziwa moc Grida polega na tym, że możesz umieszczać elementy w dowolnym miejscu — a nawet sprawiać, że będą obejmować wiele ścieżek. Właściwościami są grid-column i grid-row, które odwołują się do linii siatki (linie między ścieżkami, numerowane od 1).

css
/* Dashboard gdzie pierwsza karta obejmuje 2 kolumny */
.dashboard {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 1rem;
}

.card--featured {
  grid-column: 1 / 3;   /* start at line 1, end at line 3 */
}

/* span keyword: simpler when you don't care about exact position */
.card--featured {
  grid-column: span 2;  /* occupy 2 column tracks from wherever it lands */
}

/* Spanning rows too */
.card--tall {
  grid-row: span 2;     /* occupy 2 row tracks */
}

/* Explicit positioning: place a panel in column 3-5, row 1-2 */
.sidebar {
  grid-column: 3 / 5;
  grid-row: 1 / 2;
}

Numery linii zaczynają się od 1 dla lewej/górnej krawędzi i idą do n+1, gdzie n to liczba ścieżek. Ujemne numery linii liczą się od prawej/dolnej strony: grid-column: 1 / -1 rozciąga element na pełną szerokość siatki niezależnie od liczby kolumn — przydatna sztuczka dla elementów pełnej szerokości wewnątrz kontenera wielokolumnowego.

grid-template-areas — nazwij swój układ

To jest funkcja, która sprawia, że Grid jest naprawdę świetny dla układów na poziomie strony, i jest to najbardziej czytelne API w całym CSS. Zamiast rozmieszczać elementy według numerów linii, rysujesz układ jako grafikę ASCII w CSS, a następnie mówisz każdemu elementowi, który nazwany obszar zajmuje. Specyfikacja grid-template-areas wymaga, aby każdy wiersz miał tę samą liczbę komórek, a każdy nazwany obszar musi być prostokątny — bez kształtów L.

css
/* Full page layout: header, sidebar, main, footer */
.page-shell {
  display: grid;
  grid-template-columns: 240px 1fr;
  grid-template-rows: 60px 1fr 50px;
  grid-template-areas:
    "header  header"
    "sidebar main"
    "footer  footer";
  min-height: 100vh;
  gap: 0;
}

.page-header  { grid-area: header; }
.page-sidebar { grid-area: sidebar; }
.page-main    { grid-area: main; }
.page-footer  { grid-area: footer; }
html
<div class="page-shell">
  <header class="page-header">Site header / nav</header>
  <nav class="page-sidebar">Sidebar navigation</nav>
  <main class="page-main">Primary content</main>
  <footer class="page-footer">Footer</footer>
</div>

Kropka (.) w ciągu szablonu pozostawia komórkę celowo pustą. Możesz również układać tę samą nazwę w wielu komórkach, aby je obejmować — dokładnie to robi "header header" powyżej. Gdy zmieniasz rozmiar lub restrukturyzujesz układ dla różnych punktów widzenia, aktualizujesz ciąg szablonu w jednym miejscu, zamiast szukać po wielu deklaracjach grid-column.

auto-fill kontra auto-fit z minmax()

Wzorzec stojący za responsywnymi siatkami bez zapytań mediów to jedna z najbardziej praktycznych sztuczek Grida: repeat(auto-fill, minmax(250px, 1fr)). Rozbijmy to — minmax(250px, 1fr) mówi, że każda kolumna powinna mieć co najmniej 250px szerokości, ale rozszerzać się, aby wypełnić dostępną przestrzeń. auto-fill mówi Gridowi, aby tworzył tyle kolumn, ile się zmieści w kontenerze przy tym ograniczeniu. Wynikiem jest siatka, która automatycznie zyskuje i traci kolumny w miarę zmiany widoku.

css
/* Product card grid — reflows automatically, no media queries */
.product-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: 1.5rem;
}

/* auto-fit collapses empty tracks — items stretch to fill the container */
.product-grid--stretch {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 1.5rem;
}

Różnica między auto-fill i auto-fit jest subtelna, ale ma znaczenie, gdy jest mniej elementów niż kolumn. Z auto-fill Grid rezerwuje miejsce dla potencjalnych ścieżek nawet jeśli są puste — elementy pozostają przy minimalnej szerokości. Z auto-fit puste ścieżki zwijają się do zera, a elementy rozszerzają się, aby wypełnić wiersz. W przypadku siatek produktów i galerii, gdzie chcesz, aby elementy zajmowały dostępną przestrzeń, auto-fit zazwyczaj wygląda lepiej. W przypadku siatek, gdzie ważniejsza jest spójna szerokość elementów, właściwym wyborem jest auto-fill.

Uwaga dotycząca obsługi przeglądarek: auto-fill i auto-fit z minmax() mają doskonałą obsługę — 97%+ globalnie według caniuse. Możesz używać tego wzorca już dziś bez fallbacku w każdym nowoczesnym projekcie.

Wyrównanie w Gridzie

Grid używa tego samego modułu Box Alignment co Flexbox, więc nazwy właściwości mapują się bezpośrednio, jeśli znasz już Flexbox. Kluczowy model mentalny: justify działa na osi inline (poziomej w większości trybów pisania), align działa na osi blokowej (pionowej).

  • justify-items / align-items — domyślne wyrównanie dla wszystkich elementów w ich komórce siatki. Domyślnie jest stretch, dlatego elementy siatki automatycznie wypełniają swoją komórkę.
  • justify-content / align-content — rozdziela same ścieżki w kontenerze, gdy siatka jest mniejsza niż kontener (podobnie do justify-content we Flexboksie na kontenerze flex).
  • justify-self / align-self — nadpisuje wyrównanie dla pojedynczego elementu. Przydatne, gdy jeden element musi być wyśrodkowany, podczas gdy inne są rozciągnięte.
  • Wartości: start, end, center, stretch, space-between, space-around, space-evenly (ostatnie trzy tylko dla *-content).
css
/* Center all card content within its cell */
.card-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  align-items: start;     /* cards align to their cell top, not stretched */
  gap: 1.5rem;
}

/* Center a single call-to-action card both ways */
.card--cta {
  justify-self: center;
  align-self: center;
}

/* Distribute tracks with space between — useful for nav bars */
.nav-grid {
  display: grid;
  grid-template-columns: auto auto auto;
  justify-content: space-between;
}

Jeśli przychodzisz z Flexboksa, główna różnica polega na tym, że wyrównanie w Gridzie działa w przestrzeni dwuwymiarowej — możesz kontrolować obie osie jednocześnie. We Flexboksie wyrównanie osi poprzecznej dotyczy całego wiersza lub kolumny elementów; w Gridzie każdy element ma własną komórkę, więc justify-self i align-self dają kontrolę na poziomie elementu bez potrzeby używania elementów opakowujących.

Prawdziwy układ: kompletna powłoka dashboardu

Oto kompletna struktura strony CSS Grid dla aplikacji dashboardu — nagłówek, zwijanv pasek nawigacyjny, główny obszar treści z własną wewnętrzną siatką i stopka. To wzorzec, którego użyłbyś jako zewnętrzną powłokę układu dla dashboardu SaaS lub panelu administracyjnego.

css
/* ── Dashboard shell ──────────────────────────────────── */
.dashboard-shell {
  display: grid;
  grid-template-columns: 220px 1fr;
  grid-template-rows: 56px 1fr 44px;
  grid-template-areas:
    "topbar  topbar"
    "sidenav content"
    "sidenav footer";
  min-height: 100vh;
}

.dashboard-topbar  { grid-area: topbar;  background: #1e293b; }
.dashboard-sidenav { grid-area: sidenav; background: #0f172a; overflow-y: auto; }
.dashboard-content { grid-area: content; padding: 1.5rem; overflow-y: auto; }
.dashboard-footer  { grid-area: footer;  background: #f8fafc; border-top: 1px solid #e2e8f0; }

/* ── Collapsed sidebar variant ────────────────────────── */
.dashboard-shell--collapsed {
  grid-template-columns: 56px 1fr;
}

/* ── Content area — internal card grid ─────────────────── */
.dashboard-content {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  grid-template-rows: auto;
  align-content: start;
  gap: 1.25rem;
}

/* Stat cards take one column each; the main chart spans all */
.stat-card  { /* auto-placed, one column */ }
.main-chart { grid-column: 1 / -1; }   /* always full width */

/* ── Responsive: collapse to single-column on small screens */
@media (max-width: 768px) {
  .dashboard-shell {
    grid-template-columns: 1fr;
    grid-template-rows: 56px 1fr auto 44px;
    grid-template-areas:
      "topbar"
      "content"
      "sidenav"
      "footer";
  }
}
html
<div class="dashboard-shell">
  <header class="dashboard-topbar">
    <a href="/dashboard" class="brand">AppName</a>
    <nav class="topbar-actions">
      <button class="btn-icon" aria-label="Notifications">
        <span class="icon-bell"></span>
      </button>
      <img src="/avatar/user.png" class="user-avatar" alt="User menu">
    </nav>
  </header>

  <nav class="dashboard-sidenav">
    <ul>
      <li><a href="/dashboard">Overview</a></li>
      <li><a href="/dashboard/analytics">Analytics</a></li>
      <li><a href="/dashboard/users">Users</a></li>
      <li><a href="/dashboard/settings">Settings</a></li>
    </ul>
  </nav>

  <main class="dashboard-content">
    <div class="stat-card">Monthly Revenue</div>
    <div class="stat-card">Active Users</div>
    <div class="stat-card">Churn Rate</div>
    <div class="main-chart">Revenue Chart (full width)</div>
  </main>

  <footer class="dashboard-footer">
    <p>AppName v2.4.1 &mdash; <a href="/status">System Status</a></p>
  </footer>
</div>

Kilka rzeczy wartych odnotowania w tym przykładzie. Stopka jest umieszczona w kolumnie sidenav na dużych ekranach dzięki definicji obszaru szablonu, co utrzymuje ją wizualnie oddzieloną od przewijającego się obszaru głównej treści. 1fr na wierszach i overflow-y: auto na panelu treści oznacza, że obszar treści przewija się niezależnie — topbar i sidenav pozostają na miejscu, co jest standardowym UX dashboardu. Zapytanie @media na dole zmienia kolejność obszarów siatki dla urządzeń mobilnych bez dotykania HTML.

Podsumowanie

Krzywa uczenia się CSS Grid pochodzi głównie z nazewnictwa — linie, ścieżki, obszary, jednostki fr — ale gdy model zaskoczy, jest niezwykle bezpośredni. W przypadku prawdziwych układów: używaj grid-template-areas dla powłok stron (czyta się jak makieta), używaj repeat(auto-fit, minmax()) dla responsywnych siatek kart i używaj jawnego grid-column: span N dla elementów, które przełamują standardowy przepływ. Nie potrzebujesz całego API Grida do 90% prawdziwej pracy — te wzorce obejmują większość z tego.

Do dalszego czytania: Kompletny przewodnik CSS-Tricks po Gridzie to najlepsza jednostronicowa referencja; dokumentacja MDN CSS Grid zagłębia się w każdą właściwość; a specyfikacja W3C CSS Grid Level 1 jest zaskakująco czytelna, jeśli chcesz zrozumieć dokładne zachowanie przypadków brzegowych. Zgodność przeglądarek jest doskonała — sprawdź caniuse.com/css-grid dla aktualnych liczb. Jeśli pracujesz nad projektem na żywo i chcesz wyczyścić swój CSS, Formater CSS na tej stronie porządkuje i upiększa arkusze stylów.