CSS Grid vs Flexbox: правила вибору та рецепти макетів, що витримують продакшен

Було корисно?

Усі люблять демонстрації макетів на CSS. Потім з’являється ваш реальний додаток: динамічний контент, локалізація, feature‑флаги, A/B тести, віджети сторонніх сервісів і чиєсь «маленьке» повідомлення, що раптом розтягується на три рядки. Раптом ваш «чистий» макет починає вести себе як ігровий автомат.

Це прагматичний посібник: коли вибирати Grid, коли Flexbox — відповідний інструмент, як швидко діагностувати проблеми та набір рецептів, які тримаються, коли дані починають поводитися дивно.

Правила вибору, якими реально можна користуватися

Якщо нічого іншого не запам’ятаєте, запам’ятайте це: Flexbox — для рядка. Grid — для карти. Обидва можуть виконувати роботу іншого, але ви заплатите за це складністю, сюрпризами й прокльонами майбутнього себе на адресу минулого себе.

Правило 1: Якщо важливі й рядки, і колонки — починайте з Grid

Усе, що на зразок дашборда, оболонки додатка, галереї зображень, списку продуктів, таблиці цін або форми з вирівняними підписами й полями — це вирівнювання в двох вимірах. Ви хочете, щоб колонки вирівнювалися по рядках, а рядки — по колонках. Grid вирішує це напряму.

Flexbox може це підробити. Він також дозволить кожному рядку обчислювати «найкращий» розмір самостійно, і саме тому другий рядок не вирівнюється з першим. Питання «чому колонка 2 не вирівняна?» — це проблема форми Grid.

Правило 2: Якщо головна вісь — це історія, використовуйте Flexbox

Навбар, ряд кнопок, панелі інструментів, списки тегів, хлібні крихти, медіа-об’єкти (іконка + текст) і «на мобайлі стекаємо картки, на десктопі — в ряд» — це переважно одномірні випадки. Flexbox блискуче розподіляє вільний простір, вирівнює елементи по перетинній осі і добре реагує на змінний розмір контенту.

Flexbox також більш стійкий, коли елементи перенаповнюються. Grid може обертатися теж (з авто‑розміщенням), але якщо ви використовуєте Grid тільки заради gap і простого центровання по одній осі, ви платите зайве.

Правило 3: Якщо потрібне явне розміщення — перемагає Grid; якщо потрібен потік, керований контентом — Flexbox

Grid дозволяє іменувати області й розміщувати компоненти точно: тут хедер, там сайдбар, там main, тут футер. Це дружній до операцій стиль: передбачувано, легко інспектувати і стабільно.

Flexbox кращий, коли ви не знаєте, скільки буде елементів, і не хочете їх явно позиціонувати. Думайте: список «пігулок», тулбар з опціональними кнопками, ряд карток — «скільки поміститься».

Правило 4: Якщо важливі колонки однакової висоти — не боріться з браузером, використовуйте Grid

Flexbox може зробити однакову висоту в багатьох випадках, але легко загнатися у «чому справа, чому це не тягнеться?» через align-items, align-content і поведінку мінімальних розмірів. Модель треків Grid створена для послідовних рядків/стовпців.

Правило 5: Якщо вирівнюєте базові лінії тексту — надавайте перевагу Flexbox (або inline‑верстці)

Flexbox добре підтримує вирівнювання по базовій лінії через align-items: baseline. У Grid теж є вирівнювання по базовій лінії, але його рідше використовують і його легше неправильно зрозуміти. Для інлайн‑інтерфейсу (іконка поруч з текстом) Flexbox зазвичай простіше.

Правило 6: Якщо макет — це компонент, починайте з Flexbox; якщо це сторінка — з Grid

Компоненти зазвичай лінійні: хедер/тіло/фут картки стекаються, ряд дій модального вікна, елемент списку з іконкою/заголовком/мета. Тут Flexbox — дефолт.

Сторінки та екрани — це області з відносинами: оболонка додатка, дашборд, сторінка налаштувань із колонками, контент плюс бічна панель. Grid — дефолт.

Правило 7: Якщо у вашому макеті є «дірки» — Grid

«Дірки», як‑от: великий герой охоплює дві колонки, а навколо нього заповнюють менші елементи; діаграма займає два рядки; сайдбар займає високий лівий стовпець, а основний контент міняється. Це — природна зона для Grid. Flexbox не зробить «дірок» без хаки.

Правило 8: Якщо вас заманює переупорядкування — зупиніться і обміркуйте

І Grid, і Flexbox можуть візуально переупорядкувати елементи. Утримуйтеся від використання order чи інших технік для зміни сенсу DOM. Це поширена пастка для доступності і податок на підтримку. Застосовуйте переупорядкування лише коли порядок в DOM вже підходить для читання й фокусу.

Цитата, яку варто приколоти до монітора — бо збої макета це збої надійності в іншому вбранні: «Сподівання — це не стратегія.» — генерал Гордон Р. Салліван

Жарт №1: Flexbox — як груповий чат: класно, поки всі не починають «переноситися», і ніхто не може домовитися про вирівнювання.

Ментальні моделі: за що насправді відповідають Grid і Flexbox

Flexbox: розподіл простору по одній осі, потім вирівнювання по іншій

Flexbox відповідає на питання: «Є ряд (або колонка) елементів — як їх розмірити й розподілити залишковий простір?» У нього модель переговорів: кожен елемент має базовий розмір, потім flex-grow/flex-shrink вирішує конфлікти, а потім відбувається вирівнювання.

Ключові поведінки, які варто засвоїти:

  • Головний розмір узгоджується. flex: 1 — це не «займи весь простір», це «бери участь у розподілі вільного простору з цими вагами».
  • За замовчуванням мінімальні розміри кусають. Flex-елементи мають авто-минімальний розмір, який часто запобігає стисканню довгого контенту. Це класичне «чому він не зменшується?» — лікується min-width: 0.
  • Перенос створює кілька рядків із окремими рішеннями. Кожен рядок — свій контекст форматування flex для багатьох обчислень. Ось чому «колонки вирівнюються через рядки» — не обіцянка Flexbox.

Grid: визначте треки (ряди/колонки), потім розміщуйте елементи в матрицю

Grid відповідає на питання: «Яка структура цього простору?» Ви визначаєте колонки/ряди (явна сітка) і дозволяєте елементам авто‑розміщуватися або явно їх позиціонувати. Розміри треків можуть ґрунтуватися на контенті (max-content, min-content), дробах (fr), фіксовані або адаптивні.

Підписні сильні сторони Grid:

  • Двовимірне вирівнювання — нативне. Колонки вирівнюються через рядки, бо треки спільні.
  • Розміщення — явне. Template areas читаються як діаграма сторінки і легко пояснюються під час інцидентів.
  • Gap — першокласна властивість. gap працює чисто без дивної колапсації margin.

Що спільного: сучасна CSS‑верстка — це розв’язування обмежень

Обидві системи вирішують обмеження: доступний простір, внутрішні розміри, min/max і вирівнювання. Коли ви бачите «рандомний» макет, він рідко випадковий — зазвичай це обмеження, яке ви не помітили.

Практично, ставтесь до макетів так само, як до планування потужності: визначайте обґрунтовані обмеження і дозволяйте системі обробляти нормальні коливання. Не закладайте припущень про довжину тексту, розміри зображень або «цей лейбл ніколи не зміниться». Саме так ви відправите часову бомбу до локалізації.

Факти та історія, що пояснюють сучасну поведінку

Трохи контексту допоможе перетворити дивні особливості з чорної магії на «ах, ось чому воно так зроблено». Ось конкретні факти, які варто запам’ятати.

  1. Flexbox мав дві великі епохи в специфікації. Ранні реалізації (синтаксис 2009/2011) поводилися інакше, тому старі пости в блогах згадують властивості, які не слід копіювати.
  2. Grid був спроектований, щоб замінити звичні хакі з float. До Grid багатоколонкові макети робилися через float, display: table або «clearfix». Grid зробив ці патерни застарілими.
  3. IE11 підтримував застарілу специфікацію Grid. Модель -ms-grid відрізнялася суттєво, що роками формувало у команд враження «Grid ризиковий» — навіть після стабілізації браузерів.
  4. gap починався як фіча Grid. Пізніше він став доступний і для Flexbox у сучасних браузерах, що ліквідувало одну велику причину використовувати margin для відступів (і страждати від цього).
  5. Subgrid був довгоочікуваною відсутньою частиною. Неможливість вкладеним грідам наслідувати розміри треків спричиняла багато незручних обгорток. Підтримка subgrid покращилася, але варто перевіряти ваші цільові браузери.
  6. Стандартні мінімальні розміри flex-елементів навмисно консервативні. Поведінка auto min-size запобігає надмірному стисканню контенту, але дивує інженерів, які очікують «shrink = shrink».
  7. Одиниця fr у Grid — не «відсоток». Частки розподіляють залишковий простір після фіксованого й внутрішнього розмірювання — тонка різниця, що має значення в змішаних макетах.
  8. Авто‑розміщення в Grid — власний алгоритм. Щільне пакування (grid-auto-flow: dense) може змінити візуальний порядок, що впливає на читабельність і може збивати QA, якщо його використовувати бездумно.
  9. Обидва підходи тепер добре інтегровані в DevTools. Сучасні інструменти показують лінії гріду, розміри треків і внесок flex-елементів — дебаг макетів вже не «дивися на нього, поки не спрацює».

Типові рецепти макетів (з жорсткими краями)

1) Оболонка додатка: header + sidebar + main + footer (Grid)

Канонічний випадок для Grid: іменовані області, чіткі відносини, стабільність у масштабі.

cr0x@server:~$ cat app-shell.css
.app {
  min-height: 100vh;
  display: grid;
  grid-template-columns: 280px 1fr;
  grid-template-rows: auto 1fr auto;
  grid-template-areas:
    "header header"
    "sidebar main"
    "footer footer";
  gap: 16px;
}

.header { grid-area: header; }
.sidebar { grid-area: sidebar; }
.main { grid-area: main; min-width: 0; }
.footer { grid-area: footer; }

@media (max-width: 900px) {
  .app {
    grid-template-columns: 1fr;
    grid-template-areas:
      "header"
      "main"
      "footer";
  }
  .sidebar { display: none; }
}
...output...

Жорстка порада: min-width: 0 на головному контенті запобігає тому, щоб довгі таблиці/блоки коду змушували сторінку прокручуватись по горизонталі. Без цього ви звинуватите Grid у тому, що насправді викликано внутрішніми розмірами.

2) Панель інструментів з опціональними кнопками (Flexbox)

Коли кнопки з’являються/зникають (права доступу, feature‑флаги), Flexbox впорається плавно.

cr0x@server:~$ cat toolbar.css
.toolbar {
  display: flex;
  align-items: center;
  gap: 8px;
}
.toolbar .spacer {
  margin-left: auto;
}
...output...

Жорстка порада: Не використовуйте justify-content: space-between, якщо середній контент може переноситися; ви отримаєте дивні «телепортації» відступів. Краще використовувати spacer.

3) Центрування модального вікна (Grid або Flexbox; оберіть один і стандартизуйте)

Обидва підходи працюють. У продакшені оберіть той, який ваша команда найшвидше дебагує.

cr0x@server:~$ cat center.css
.overlay {
  display: grid;
  place-items: center;
  padding: 24px;
}
.modal {
  width: min(720px, 100%);
}
...output...

Жорстка порада: Додавайте padding на overlay, щоб на малих екранах модалка не притиснулась до країв.

4) Адаптивна сітка карток «скільки поміститься» (Grid)

Це патерн repeat(auto-fit, minmax()). Нудно, швидко і важко зламати.

cr0x@server:~$ cat cards.css
.cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 16px;
}
.card {
  display: flex;
  flex-direction: column;
  min-width: 0;
}
...output...

Жорстка порада: Всередині кожної картки використовуйте Flexbox для вертикальної структури. Комбінація «Grid зовні, Flex всередині» — надійна.

5) Media‑об’єкт: іконка + контент (Flexbox)

Класика: аватар + блок тексту або іконка статусу + повідомлення.

cr0x@server:~$ cat media-object.css
.media {
  display: flex;
  gap: 12px;
  align-items: flex-start;
}
.media .icon {
  flex: 0 0 auto;
}
.media .content {
  flex: 1 1 auto;
  min-width: 0;
}
...output...

Жорстка порада: min-width: 0 на блоці контенту запобігає тому, щоб довгі нерозривні рядки розширювали рядок.

6) Верстка форми з вирівняними підписами (Grid)

Якщо потрібно, щоб підписи та поля вирівнювалися по багатьох рядках — Grid тут дорослий вибір.

cr0x@server:~$ cat form.css
.form {
  display: grid;
  grid-template-columns: 180px 1fr;
  gap: 12px 16px;
  align-items: center;
}
.form label {
  justify-self: end;
}
@media (max-width: 600px) {
  .form {
    grid-template-columns: 1fr;
  }
  .form label {
    justify-self: start;
  }
}
...output...

Жорстка порада: Для повідомлень про помилки розміщуйте їх як елементи на повний рядок з обхопленням колонок, інакше ви отримаєте зсуви вирівнювання.

7) Прилиплий футер (Sticky footer) (Flexbox)

Просто, надійно, мінімум коду.

cr0x@server:~$ cat sticky-footer.css
.page {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}
.page main {
  flex: 1 0 auto;
}
.page footer {
  flex: 0 0 auto;
}
...output...

8) Макет типу «masonry»: не підробляйте його Grid’ом, якщо ви цього не маєте на увазі

Якщо ви намагаєтеся впакувати картки змінної висоти як Pinterest, Grid за замовчуванням не дає «masonry». Ви можете наблизитися, але натрапите на прогалини і проблеми з порядком. Використовуйте справжні masonry‑фічі там, де доступні, або погоджуйтеся на стандартну сітку.

9) Вирівнювання заголовка та тіла таблиці даних (Grid‑обгортка)

Рідні таблиці роблять багато чого за вас. Але якщо ви будуєте «табличний» UI для віртуалізації, Grid часто використовують, щоб тримати колонки вирівняними.

cr0x@server:~$ cat virtual-table.css
.table {
  display: grid;
  grid-template-columns: 160px 1fr 120px 140px;
}
.row {
  display: contents;
}
.cell {
  padding: 8px 12px;
  border-bottom: 1px solid #e6e6e6;
  min-width: 0;
}
...output...

Жорстка порада: display: contents може мати наслідки для доступності та інструментів. Перевіряйте з екранними рідерами та стилями фокусу.

10) «Holy grail» адаптивний макет (Grid з template areas)

Template areas — рідкість серед CSS‑фіч, яка зрозуміла під час інциденту.

cr0x@server:~$ cat holy-grail.css
.shell {
  display: grid;
  grid-template-columns: 240px 1fr 280px;
  grid-template-rows: auto 1fr auto;
  grid-template-areas:
    "header header header"
    "nav main aside"
    "footer footer footer";
  gap: 16px;
}
@media (max-width: 1000px) {
  .shell {
    grid-template-columns: 1fr;
    grid-template-areas:
      "header"
      "nav"
      "main"
      "aside"
      "footer";
  }
}
...output...

Практичні завдання для дебагу: команди, результати, рішення

Ви не налагоджуєте продакшн‑системи на відчуттях. Макети не мають бути іншими. Ось практичні завдання, які можна виконати локально (або в CI через Playwright), щоб виявити і діагностувати помилки Grid/Flex.

Завдання 1: Визначити, які елементи насправді є контейнерами flex/grid

cr0x@server:~$ node -e "const {JSDOM}=require('jsdom'); const html=\`
\`; const dom=new JSDOM(html); console.log(dom.window.document.querySelector('.app').className);" app

Що означає вивід: Це підтверджує, що ви запитуєте потрібний контейнер у тулінгах/скриптах.

Рішення: Якщо селектор не влучає в потрібний нод, ваше «виправлення макета» ймовірно застосовано не до того елемента (часто трапляється в бібліотеках компонентів із обгортками).

Завдання 2: Перевірити обчислені значення display (спіймати «display перевизначено»)

cr0x@server:~$ node -e "const {JSDOM}=require('jsdom'); const dom=new JSDOM('
',{pretendToBeVisual:true}); const el=dom.window.document.querySelector('div'); console.log(dom.window.getComputedStyle(el).display);" block

Що означає вивід: Пізніші класи перемагають; ваш контейнер вже не grid.

Рішення: Виправте специфічність/порядок або видаліть конфліктуючий утилітний клас. Несподівано багато інцидентів «Grid не працює» викликані тим, що хтось додав .d-block.

Завдання 3: Підтвердити, що grid-template-columns вирішується як очікується

cr0x@server:~$ node -e "const {JSDOM}=require('jsdom'); const dom=new JSDOM('
',{pretendToBeVisual:true}); const el=dom.window.document.querySelector('.g'); console.log(dom.window.getComputedStyle(el).gridTemplateColumns);" 200px 1fr

Що означає вивід: Ви бачите задеклароване значення; у реальному DevTools ви також побачите розміри треків у пікселях.

Рішення: Якщо це показує none або неочікувані колонки — правило не застосовано. Знайдіть переопределення.

Завдання 4: Виявити flex‑елементи, що відмовляються стискатися (пастка min-width)

cr0x@server:~$ node -e "const {JSDOM}=require('jsdom'); const html=\`

this-is-a-very-long-unbroken-string-that-wants-to-overflow
\`; const dom=new JSDOM(html,{pretendToBeVisual:true}); const el=dom.window.document.querySelector('.a'); console.log(dom.window.getComputedStyle(el).minWidth);" auto

Що означає вивід: Мінімальна ширина за замовчуванням — auto, що може перешкоджати стисканню нижче розміру контенту.

Рішення: Додайте min-width: 0 (або overflow: hidden з переносом тексту) тому flex‑дочці, яка має стискатися.

Завдання 5: Перевірити підтримку gap у вашому рантаймі браузері (CI smoke check)

cr0x@server:~$ node -e "console.log('gap in flex is runtime/browser dependent; verify with Playwright in CI, not Node.');"
gap in flex is runtime/browser dependent; verify with Playwright in CI, not Node.

Що означає вивід: Node не покаже вам підтримку макетів у браузері; запускайте тест у реальному браузері.

Рішення: Додайте перевірку у CI (Playwright), якщо ви підтримуєте старі вбудовані браузери. Якщо ні — стандартизуйтеся на gap і видаліть margin‑хаки.

Завдання 6: Використати Playwright для скриншотів у матриці брейкпоінтів

cr0x@server:~$ npx playwright --version
Version 1.49.0

Що означає вивід: Інструменти присутні; ви можете запускати візуальні перевірки.

Рішення: Якщо у вас немає регресійних скриншотів, додайте їх для ваших топ‑5 сторінок з важкими макетами. Помилки макетів дешевше ловити візуально, ніж зі скарг користувачів.

cr0x@server:~$ cat playwright-layout-check.mjs
import { chromium } from 'playwright';

const viewports = [
  { width: 375, height: 812 },
  { width: 768, height: 1024 },
  { width: 1280, height: 800 }
];

const url = 'http://localhost:3000/dashboard';

const browser = await chromium.launch();
const page = await browser.newPage();

for (const vp of viewports) {
  await page.setViewportSize(vp);
  await page.goto(url, { waitUntil: 'networkidle' });
  await page.waitForTimeout(250);
  await page.screenshot({ path: `layout-${vp.width}x${vp.height}.png`, fullPage: true });
  console.log(`captured ${vp.width}x${vp.height}`);
}

await browser.close();
...output...

Що означає вивід: Кожен рядок «captured …» означає, що ви отримали артефакт для відповідного брейкпоінта.

Рішення: Якщо на скриншоті брейкпоінта видно перекриття/переповнення, вирішіть, чи це проблема розмірів треків Grid (виправлення Grid), чи мінімальних розмірів/overflow у Flexbox (виправлення Flex).

Завдання 7: Перевірити несподіване горизонтальне переповнення в рантаймі

cr0x@server:~$ cat overflow-check.js
(() => {
  const doc = document.documentElement;
  const maxRight = [...document.querySelectorAll('*')].reduce((m, el) => {
    const r = el.getBoundingClientRect();
    return Math.max(m, r.right);
  }, 0);
  return { viewport: window.innerWidth, maxRight, overflowPx: Math.max(0, maxRight - window.innerWidth) };
})();
...output...

Що означає вивід: У консолі DevTools ви отримаєте об’єкт типу {overflowPx: 24}.

Рішення: Якщо є переповнення, знайдіть елемент, що це викликає, і застосуйте min-width: 0, max-width: 100% або змініть треки Grid, щоб уникнути внутрішнього розширення.

Завдання 8: Знайти найширший одиночний елемент (часто довгий рядок)

cr0x@server:~$ cat widest-element.js
(() => {
  const els = [...document.querySelectorAll('body *')];
  let worst = null;
  for (const el of els) {
    const r = el.getBoundingClientRect();
    if (!worst || r.width > worst.r.width) worst = { el, r };
  }
  return { tag: worst.el.tagName, class: worst.el.className, width: worst.r.width };
})();
...output...

Що означає вивід: Ви отримаєте тег/клас і ширину в пікселях.

Рішення: Якщо це flex‑дитина — додайте min-width: 0. Якщо це grid‑елемент — перевірте визначення треків і внутрішні розміри; розгляньте minmax(0, 1fr) замість простого 1fr у деяких випадках.

Завдання 9: Перевірити, чи Grid‑елемент випадково не охоплює зайві треки

cr0x@server:~$ cat grid-span-check.js
(() => {
  const el = document.querySelector('.suspect');
  const cs = getComputedStyle(el);
  return { gridColumnStart: cs.gridColumnStart, gridColumnEnd: cs.gridColumnEnd };
})();
...output...

Що означає вивід: Якщо бачите 1 / -1, він охоплює повну ширину.

Рішення: Якщо він не має так охоплювати — видаліть правило span або обмежте його потрібним брейкпоінтом.

Завдання 10: Підтвердити, що поведінка перенесення flex не викликає невідповідностей

cr0x@server:~$ cat flex-wrap-check.js
(() => {
  const el = document.querySelector('.row');
  const cs = getComputedStyle(el);
  return { flexWrap: cs.flexWrap, justifyContent: cs.justifyContent, alignItems: cs.alignItems };
})();
...output...

Що означає вивід: Ви побачите, чи ввімкнено перенесення і як налаштовано вирівнювання.

Рішення: Якщо перенесення увімкнено і вам потрібне вирівнювання колонок через рядки — перейдіть з зовнішнього контейнера на Grid.

Завдання 11: Виявити ризик thrash‑верстки (resize observers + залежності від layout)

cr0x@server:~$ cat perf-layout-thrash-sniff.js
(() => {
  performance.mark('t0');
  for (let i = 0; i < 2000; i++) {
    document.body.offsetHeight;
  }
  performance.mark('t1');
  performance.measure('read-layout-loop', 't0', 't1');
  return performance.getEntriesByName('read-layout-loop').pop().duration;
})();
...output...

Що означає вивід: У DevTools ви отримаєте тривалість у мілісекундах. Великі значення вказують на дорогі примусово‑обчислювані макети.

Рішення: Якщо макет дорогий, уникайте JS‑вимірювань під час анімацій; віддавайте перевагу правилам CSS Grid/Flex, які не вимагають ручних вимірів.

Завдання 12: Підтвердити, що брейкпоінти реально застосовуються (сана перевірка медіа‑запитів)

cr0x@server:~$ cat media-query-check.js
(() => ({
  isMobile: matchMedia('(max-width: 600px)').matches,
  isTablet: matchMedia('(max-width: 900px)').matches
}))();
...output...

Що означає вивід: Булеві прапори відображають активні брейкпоінти у поточному вікні перегляду.

Рішення: Якщо активний неправильний брейкпоінт — ваш тест‑вьюпорт не той, який ви думаєте, або ваш CSS використовує інші значення брейкпоінтів, ніж дизайн‑система.

Швидкий план діагностики

Це шлях «звільни мене за десять хвилин». Використовуйте, коли баг макета в продакшені і треба знайти вузьке місце до того, як почнете гадати.

Спочатку: знайдіть контейнер і його режим макета

  1. У DevTools клікніть зламаний елемент і знайдіть найближчого батька, який відповідає за макет.
  2. Перевірте обчислений display. Це grid, flex чи ні?
  3. Перевірте, чи утилітний клас або обгортка компонента не перевизначають display на брейкпоінті.

Рішення: Якщо контейнер насправді не grid/flex — зупиніться. Виправте каскад/порядок спочатку. Логіка макета не працює, якщо режим вимкнено.

По‑друге: перевірте переповнення і обмеження мін/макс розмірів

  1. Шукайте горизонтальні скролбарі. Запустіть скрипт «overflow check», якщо потрібно.
  2. Інспектуйте найширшу дитину; довгі рядки, блоки коду та зображення — звичайні підозрювані.
  3. Для Flexbox: застосуйте min-width: 0 до дітей, які мають стискатися. Для Grid: розгляньте minmax(0, 1fr) або явні max‑ширини.

Рішення: Якщо переповнення зумовлене внутрішніми розмірами, ваше виправлення майже ніколи не «додати ще одну обгортку». Це робота з min/max обмеженнями.

По‑третє: перевірте розміри треків і логіку розміщення (Grid) або переговори flex (Flex)

  • Grid: перевірте grid-template-columns, авто‑розміщення і будь‑які ненавмисні spanning.
  • Flex: перевірте скорочення flex, basis, перенесення і чи не воює justify-content з реальними ширинами контенту.

Рішення: Якщо ви намагаєтеся вирівняти колонки через перенесені рядки у Flexbox — припиніть співати з реальністю і перейдіть на Grid.

По‑четверте: відтворіть у найменшому тестовому випадку

Зведіть до одного контейнера і трьох елементів. Якщо баг зникає — це не базова модель макета, а каскад, обгортка або внутрішній розмір дитини.

Типові помилки: симптом → корінь проблеми → виправлення

1) «Мій flex‑елемент не стискається; він виходить за межі контейнера»

Симптом: Довгий заголовок, URL або блок коду виходить за межі контейнера; додавання flex-shrink: 1 не допомагає.

Корінь проблеми: Flex‑елементи за замовчуванням мають min-width: auto, що зберігає внутрішній розмір контенту.

Виправлення: Додайте min-width: 0 до flex‑дочок, які мають стискатися, і встановіть обтікання/еліпсис за потреби.

2) «Колонки Grid не стискаються; уся сторінка отримує горизонтальний скрол»

Симптом: Ваша колонка з 1fr несподівано розширюється, коли в ній довгий контент.

Корінь проблеми: Внутрішні розміри grid‑елементів можуть впливати на розміри треків. Деякі патерни потребують явного minmax(0, 1fr).

Виправлення: Використовуйте grid-template-columns: 280px minmax(0, 1fr) і переконайтесь, що діти можуть стискатися (min-width: 0).

3) «Я використав Flexbox для двоколонкової форми і підписи не вирівнюються»

Симптом: Кожний ряд виглядає по‑різному; підписи зсуваються залежно від контенту.

Корінь проблеми: Перенесені flex‑рядки незалежні; спільних колонок не існує.

Виправлення: Переключіть контейнер форми на Grid з двома колонками і дозвольте полям займати рядок на мобайлі.

4) «Невідповідність відступів; у останнього елемента зайвий margin»

Симптом: Списки мають дивні кінцеві відступи, особливо при переносі.

Корінь проблеми: Відступи через margin накопичуються на краях; перенос лише погіршує ситуацію.

Виправлення: Використовуйте gap на контейнері (Grid або Flex) і видаліть margin‑відступи у дітей.

5) «Елементи виглядають центрованими, але клік/вибір відчувається невірно»

Симптом: Візуальне вирівнювання відрізняється від контурів фокусу або клікабельних областей.

Корінь проблеми: Вирівнювання застосовано до обгортки, але інтерактивний елемент має власні розміри/відступи; іноді display: contents плутає контури фокусу.

Виправлення: Вирівнюйте сам інтерактивний елемент; уникайте display: contents для DOM, критичного для фокусу, якщо не перевірили навігацію клавіатурою.

6) «Grid авто‑розміщення переставляє мої картки; QA каже, що порядок неправильний»

Симптом: Візуальний порядок відрізняється від порядку в DOM.

Корінь проблеми: grid-auto-flow: dense або явні правила розміщення перепаковують елементи для щільнішого заповнення.

Виправлення: Видаліть щільне пакування для контенту, де важливий семантичний порядок. Тримайте порядок в DOM і візуальний порядок вирівняними, якщо немає вагомої причини інакше.

7) «Я використав justify-content: space-between і тепер відстані вибухнули при переносі»

Симптом: Кнопки на останньому перенесеному рядку розтягуються дивно.

Корінь проблеми: Кожен рядок flex незалежно розподіляє залишковий простір.

Виправлення: Використайте spacer (margin-left: auto) або перейдіть на Grid для передбачуванішого розподілу.

8) «Вкладені гріди конфліктують; внутрішні елементи не вирівнюються з зовнішніми колонками»

Симптом: Заголовки й поля трохи зсунуті між компонентами.

Корінь проблеми: Вкладені гріди створюють незалежні контексти треків; без subgrid ви не отримаєте спільних колонок.

Виправлення: Або переробіть дизайн, щоб уникнути вирівнювання колонок між компонентами, або використайте subgrid там, де підтримується; інакше передавайте визначення колонок через CSS‑змінні і стандартизуйте.

Чеклісти / покроковий план

Чекліст A: Вибір Grid чи Flexbox для нового макета

  1. Потрібні спільні колонки через рядки? Якщо так: Grid.
  2. Це компонент з основною віссю (ряд/колонка)? Якщо так: Flexbox.
  3. Елементи будуть переноситися і все одно потребують вирівнювання? Якщо так: Grid.
  4. Ви розміщуєте іменовані регіони (header/sidebar/main)? Якщо так: Grid з template areas.
  5. Ви здебільшого розподіляєте вільний простір між сусідами? Якщо так: Flexbox.
  6. Довжина контенту може сильно варіювати (локалізація, контент користувача)? Якщо так: додайте min-width: 0 і явні обмеження раніше.

Чекліст B: Загартування макета для продакшен‑даних

  1. Додайте реалістично довгі рядки в тестові фікстури (емейли, UUID, URL, німецькі компаунд‑слова).
  2. Тестуйте на 320px ширини та 200% зумі (доступність). Виправляйте переповнення перед релізом.
  3. Використовуйте gap для відступів; уникайте margin‑базованих «сіток», якщо не любите археологію.
  4. Встановіть min-width: 0 на flex/grid‑дітях, які мають стискатися.
  5. Обмежуйте зображення: max-width: 100% і відомі співвідношення сторін де можливо.
  6. Тримайте порядок в DOM осмисленим; уникайте візуального переупорядкування, якщо це впливає на клавіатурну навігацію.

Чекліст C: Стандартні рецепти, які має впровадити команда

  • Оболонка додатка: Grid з template areas.
  • Список карток: Grid зовні, Flex всередині.
  • Панель інструментів: Flex зі spacer‑патроном.
  • Форма: Grid з колонкою для підписів і колонкою для полів.
  • Прилиплий футер: Flex‑колонка.
  • Центрування: один стандартний патерн (Grid place‑items або Flex align/justify) і використовуйте його скрізь.

Три корпоративні міні‑історії (анонімізовані, реалістичні, технічно точні)

Історія 1: Інцидент через хибне припущення (перенесення у Flexbox)

Продуктова команда випустила сторінку «огляд акаунта». Там був акуратний ряд підсумкових тайлів: баланс, план, використання, дата наступного рахунку. Макет був на Flexbox з переносом, бо здавалося, що це просто ряд карток, які мають переноситися на маленьких екранах.

У день релізу почали приходити запити до сапорту: «Мій баланс зник» та «Дата рахунку перекриває кнопку». Інженери не могли відтворити локально. Це траплялося тільки для деяких користувачів і лише в певних мовах.

Корінь проблеми — хибне припущення: що перенос Flexbox збереже тайли у візуально вирівняній сітці. Ні. Кожен перенесений рядок сам обирав ширину за своїм контентом. У німецькій мітки були довші; ширини тайлів другого рядка змінилися. Кнопка «Pay now» всередині одного тайла мала white-space: nowrap, що змушувало тайл ширитися. Перенесення змінило порядок тайлів і спричинило накладання у зв’язку з абсолютною бейдж‑хаком.

Виправлення було нудним: переробили зовнішній контейнер на Grid з repeat(auto-fit, minmax()), прибрали абсолютний бейдж‑хак і додали min-width: 0 у потрібних місцях. Сторінка стала стабільною у різних мовах і при різних даних. Урок закріпився: якщо потрібна поведінка сітки — використовуйте Grid. Flexbox не є системою сітки; це переговорник рядка.

Історія 2: Оптимізація, що відкотилася (dense packing + візуальний reorder)

Інша команда побудувала сторінку відкриття контенту з картками різної висоти. Хтось запропонував «зробити щільніше», увімкнувши grid-auto-flow: dense. І воно справді стало щільнішим — більше контенту помістилося «вище в згортці». Дизайнер був у захваті.

Потім QA помітила дивність: навігація клавіатурою стала випадковою. Іноді Tab стрибав «назад» на екрані. Вивід екранного рідера не відповідав візуальному порядку. Користувачі повідомляли «я клікнув третю картку, а відкрився не той елемент», що здавалося неможливим, поки не спостерігали, як хтось використовує клавіатуру і бачать контури фокусу.

Dense packing переставив елементи візуально, щоб заповнити дірки. Порядок в DOM залишився правильним, але візуальний порядок тепер інший. Для миші це було в основному прийнятно; для клавіатури й допоміжних технологій — плутанина або злам. До того ж аналітика стала нечіткою, бо користувачі клікали те, що бачили, а пайплайни трекінгу припускали відповідність DOM і візуальному порядку.

Виправлення: прибрали dense packing для основного списку контенту, залишили його лише для декоративних галерей, де порядок неважливий, і додали явні стилі фокусу, які відповідали межам карток. Оптимізація підвищила щільність, але знизила надійність взаємодії — в операційному сенсі вона пожертвувала коректністю заради пропускної здатності без плану відкату.

Історія 3: Нудна практика, що врятувала день (тести контракту макета)

Одна організація отримала настільки багато проблем з регресіями макета, що почала ставитися до макета як до інтерфейсу: у нього є контракти. Не формальні специфікації — просто кілька Playwright‑тестів, які завантажували критичні сторінки з крайніми фікстурами і робили скриншоти в трьох брейкпоінтах.

Звучало бюрократично, поки це не окупилося. Рутинний рефактор замінив обгортку компонента, і утилітний клас випадково змінив display: grid на display: block на сторінці налаштувань. Локально розробник перевірив тільки «щасливий шлях» в одному вьюпорті. Все виглядало нормально при коротких підписах і без помилок валідації.

CI зловив це одразу. Diff скриншотів показав, що підписи вкладаються неправильно, а повідомлення про помилки перекривають суміжні секції. Розробник виправив порядок класів і додав регресійну фікстуру з довгими повідомленнями про помилки. Жодного інциденту, жодного хотфіксу, жодної суботньої археології «хто змінив CSS?».

Це не було гламурне інженерство. Це CSS‑еквівалент бекапів: ви цінуєте їх, тільки коли вони вас врятують. Жарт №2: регресії CSS схожі на неперевірені бекапи — всі припускають, що вони працюють, допоки раптово все не стає дуже цікаво.

FAQ

1) Чи можна використовувати Grid і Flexbox разом?

Так. Часто це найкраще рішення. Використовуйте Grid для зовнішньої структури (регіони, колонки), а Flexbox для внутрішнього вирівнювання (ряди кнопок, медіа‑об’єкти, внутрішня структура карток).

2) Чи повільніший Grid порівняно з Flexbox?

Не настільки, щоб це мало вплив на вибір у типових UI‑макетах. Обирайте модель, що відповідає задачі. Проблеми продуктивності зазвичай викликані розміром DOM, дорогими перефарбуваннями або JS‑thrash’ем, а не вибором Grid проти Flexbox.

3) Чому min-width: 0 вирішує так багато багів макета?

Тому що внутрішні розміри за замовчуванням захищають контент від надмірного стискання, але цей захист часто викликає переповнення в обмежених макетах. Встановлення min-width: 0 дозволяє елементу дійсно стискатися і дає механізмам обробки переповнення (перенос/еліпсис) робити свою роботу.

4) Чи варто замінити всі Flexbox‑макети на Grid тепер, коли Grid є?

Ні. Flexbox і надалі правильний інструмент для одномірної верстки компонентів. Перепис стабільного коду заради моди — це шлях до створення ризиків.

5) Коли слід уникати grid-template-areas?

Уникайте їх, коли розміщення дуже динамічне або повторювані патерни з невідомою кількістю елементів (наприклад, сітки карток). Template areas краще підходять для регіонів сторінки й невеликої фіксованої кількості компонентів.

6) Чи погано використовувати order у Flexbox?

Це ризиковано. Візуальне переупорядкування може конфліктувати з порядком в DOM, впливаючи на навігацію клавіатурою й екранні рідери. Якщо доводиться переупорядковувати, переконайтеся, що порядок в DOM залишається осмисленим і протестуйте потік фокусу.

7) Який найпростіший спосіб побудувати адаптивну сітку карток?

Контейнер Grid: repeat(auto-fit, minmax(220px, 1fr)) плюс gap. Залишайте внутрішню структуру картки як Flexbox‑колонку. Додавайте min-width: 0 там, де текст може переповнюватися.

8) Чому justify-content не робить те, що я очікую у Flexbox?

Тому що воно розподіляє залишковий простір по головній осі. Якщо елементи вже займають весь простір (або переносяться на кілька рядків), justify-content може здаватися «непрацюючим» або поводитися по-різному для кожного рядка.

9) Який найкращий спосіб швидко дебагнути Grid?

Увімкніть оверлей Grid у DevTools, подивіться розміри треків і перевірте на ненавмисні spans. Потім подивіться, чи не змушує треки розширюватися внутрішній контент. Більшість багів Grid — це питання розмірів, а не позиціонування.

10) Який найшвидший спосіб дебагнути Flexbox?

Використовуйте оверлей flex у DevTools, перегляньте flex‑basis/grow/shrink кожного елемента і перевірте поведінку мінімальних розмірів. Якщо щось не стискається — зазвичай це min-width або нерозривний контент.

Наступні кроки, які можна зробити сьогодні

  1. Визначте дефолти: Grid для оболонок сторінок і вирівнювання багатьох колонок; Flexbox для внутрішньої структури компонентів і тулбарів. Запишіть це в стилгайді команди.
  2. Стандартизуйте загартовані рецепти: app shell з grid‑областями, патерн сітки карток, патерн spacer для тулбарів, патерн форми з колонкою підписів.
  3. Додайте дві фікстури: одну з надзвичайно довгим текстом, одну з помилками валідації повсюдно. Проганяйте їх через скриншот‑чекі на 3 брейкпоінтах.
  4. Навчіть одну звичку дебагу: коли макет виглядає «випадковим», перевірте обчислений display і min-width перед тим, як щось змінювати.
  5. Заплатіть невеликий податок: використовуйте gap, уникайте grid‑систем на основі margin і тримайте порядок в DOM осмисленим. Це не приємна робота, але вона допомагає макетам вижити в продакшені.
← Попередня
WordPress не може завантажити зображення: швидке усунення неполадок з дозволами, лімітами та бібліотеками
Наступна →
Docker Compose: depends_on вам збрехав — правильна готовність без костилів

Залишити коментар