jvinhit//lab

Search posts

Type to search across journal entries.

navigate open esc close

Flexbox vs Grid — The Layout Mental Model Senior Frontend Engineers Actually Need

Flexbox vs Grid mental model for senior engineers: axes, flex shorthand, fr tracks, auto-fit vs auto-fill, intrinsic sizing, and production layout pitfalls.

Hai hệ thống layout, một cây quyết định

Mọi senior frontend engineer đều biết cả Flexbox và Grid. Ít người diễn đạt được khi nào nên dùng cái nào — và còn ít hơn nữa là dự đoán container sẽ behave thế nào khi content, viewport, và min-width: auto va chạm.

Bài viết này không phải bảng tra thuộc tính. Đó là một mental model để suy luận layout trong formatting context của browser: một chiều vs hai chiều, track explicit vs implicit, intrinsic vs extrinsic sizing, và các cái bẫy vẫn lọt qua code review.


Phân biệt cốt lõi: 1D vs 2D

FlexboxGrid
ChiềuMột chiều mỗi lầnHàng cột cùng lúc
Câu hỏi chính""Item chảy dọc một đường thế nào?"""Mỗi item nằm ở đâu trong ma trận?”
Tốt nhất choToolbar, nav bar, card footer, căn giữa, phân bổ không gian dọc một trụcPage shell, dashboard, gallery, form grid, mọi thứ có hai trục căn chỉnh
Xuống dòngTuỳ chọn qua flex-wrapCó sẵn — hàng/cột là first-class

Quy tắc ngón tay cái: Nếu bạn đang vật lộn với Flexbox lồng nhau để giả lập grid, hãy chuyển sang Grid. Nếu bạn chỉ cần căn hoặc phân bổ item theo một hướng, Flexbox đơn giản hơn và thường chịu thay đổi content tốt hơn.


Playground trực tiếp

Demo dưới cho phép bạn chuyển giữa Flexbox và Grid, chỉnh alignment và track property, và xem CSS rule được sinh ra theo thời gian thực. Thay đổi kích thước iframe để quan sát behavior auto-fit vs auto-fill.

Mở demo đầy đủ:


Flexbox: Trục và luồng

Flexbox hoạt động trên flex container với flex items. Mọi thứ khác — alignment, phân bổ, wrap — đều xuất phát từ hai trục.

Trục chính và trục phụ

Trục chính được định nghĩa bởi flex-direction:

flex-directionTrục chính chạy…Trục phụ chạy…
row (default)Trái → phảiTrên → dưới
row-reverseRight → leftTop → bottom
columnTop → bottomLeft → right
column-reverseBottom → topLeft → right

Đó là lý do justify-contentalign-items đổi nghĩa khi bạn đổi direction:

.toolbar {
  display: flex;
  flex-direction: row;
  justify-content: space-between; /* distributes along horizontal main axis */
  align-items: center;            /* centers vertically on cross axis */
}

.sidebar-nav {
  display: flex;
  flex-direction: column;
  justify-content: flex-start; /* now vertical — pushes items to top */
  align-items: stretch;        /* now horizontal — items fill width */
}

Mental model: justify-* luôn nhắm trục chính; align-* luôn nhắm trục phụ. Direction xoay trục — không đổi tên property.

Kích thước flex item: grow, shrink, basis

Mỗi item có ba nút điều khiển cách chia không gian:

PropertyLàm gì
flex-growHấp thụ bao nhiêu không gian thừa so với anh em (mặc định 0 = không grow)
flex-shrinkNhường bao nhiêu khi chật chỗ
flex-basisKích thước khởi điểm trước khi grow/shrink

Shorthand flex gom cả ba trong một khai báo:

/* flex: <grow> <shrink> <basis> */

.sidebar  { flex: 0 0 240px; }  /* fixed 240px, never grow or shrink */
.content  { flex: 1 1 auto; }   /* take remaining space, can shrink */
.tag      { flex: 0 1 auto; }   /* size to content, shrink if needed */
.icon     { flex: none; }       /* shorthand for 0 0 auto — rigid */

Mẹo production: flex: 1 mở rộng thành flex: 1 1 0% — basis 0% nghĩa là “bắt đầu từ zero và grow đều”. Khác với flex: 1 1 auto, tôn trọng kích thước content làm điểm bắt đầu. Trộn chúng trong một container gây cột không đều tinh vi.

flex-wrap và Flex nhiều dòng

Với flex-wrap: wrap, flex container trở thành multi-line. Mỗi dòng là một flex formatting context riêng cho cross-axis alignment.

.chip-list {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  align-content: flex-start; /* distributes LINES on cross axis, not items */
}
PropertyÁp dụng cho…
align-itemsItem trong một dòng
align-contentCác dòng khi wrap (chỉ khi còn không gian trục phụ)
justify-contentItem dọc trục chính (mỗi dòng khi wrap)

Grid: Track, cell, và implicit grid

CSS Grid định nghĩa hệ thống track hai chiều. Bạn khai báo hàng và cột; item được đặt vào cell.

Grid explicit vs implicit

Explicit grid là những gì bạn định nghĩa bằng grid-template-rows / grid-template-columns. Khi item tràn track đó, browser tạo implicit grid với track auto-sized.

.dashboard {
  display: grid;
  grid-template-columns: 240px 1fr;   /* explicit: 2 columns */
  grid-template-rows: auto 1fr auto;  /* explicit: 3 rows */
  /* items beyond row 3 → implicit rows, sized by grid-auto-rows (default: auto) */
  grid-auto-rows: minmax(80px, auto);
}
ConceptNghĩa
Explicit tracksKhai báo trong grid-template-*
Implicit tracksTạo cho item tràn
grid-auto-flow: row (default)Lấp hàng trước, rồi hàng mới
grid-auto-flow: columnLấp cột trước
grid-auto-flow: denseLấp khoảng trống bằng item nhỏ hơn

Đơn vị frminmax()

Đơn vị fr (fraction) phân bổ không gian trống còn lại sau khi trừ kích thước cố định và intrinsic.

.layout {
  display: grid;
  grid-template-columns: 200px 1fr 1fr;
  /* sidebar fixed 200px; two columns split remaining space equally */
}

.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 1.5rem;
}

minmax(min, max) đặt khoảng kích thước track:

Track definitionBehavior
1frChia đều không gian còn lại
minmax(200px, 1fr)Tối thiểu 200px, grow để lấp
minmax(0, 1fr)Có thể shrink dưới min-size content (hữu ích cho overflow)
min-contentHẹp bằng chuỗi không break dài nhất
max-contentRộng bằng content muốn

auto-fit vs auto-fill

Cả hai lặp pattern track bao nhiêu lần vừa khít. Khác biệt là điều gì xảy ra với track rỗng:

KeywordEmpty tracksHiệu ứng
auto-fillGiữ trong gridItem giữ width minmax; cột rỗng còn là ghost space
auto-fitThu về width 0Item hiện có stretch lấp hàng
/* 3 cards in a 1200px container with minmax(250px, 1fr): */

/* auto-fill → 4 columns created, 4th is empty, cards stay ~250px+ */
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));

/* auto-fit  → 3 columns, cards expand to fill full width */
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));

Khi nào dùng cái nào: auto-fit cho card grid responsive mà item nên grow lấp hàng. auto-fill khi bạn muốn số cột nhất quán và không muốn item stretch chỉ vì còn slot rỗng.

Đặt vị trí Grid

Item tự đặt mặc định. Để đặt explicit, dùng số line hoặc named area:

.page {
  display: grid;
  grid-template-areas:
    "header header"
    "sidebar main"
    "footer footer";
  grid-template-columns: 240px 1fr;
  grid-template-rows: auto 1fr auto;
  min-height: 100dvh;
}

.page > header  { grid-area: header; }
.page > aside   { grid-area: sidebar; }
.page > main    { grid-area: main; }
.page > footer  { grid-area: footer; }

grid-template-areas xuất sắc cho page shell mà topology ổn định. Với grid cấp component và số item động, ưu tiên repeat() + minmax().


Intrinsic sizing: Tầng ẩn

Cả Flexbox và Grid đều tương tác với intrinsic sizing keyword. Hiểu chúng ngăn bug “overflow bí ẩn”.

KeywordResolve thành…
min-contentKích thước tối thiểu không overflow (từ dài nhất / đoạn không break)
max-contentKích thước nếu không wrap hay bị ràng buộc
fit-contentmin(max-content, max(min-content, stretch)) — Clamp theo không gian có sẵn
.truncate-safe {
  width: fit-content;
  max-width: 100%;
}

.grid-cell {
  /* Allow track to shrink below content min-width */
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}

Trong Grid, minmax(min-content, 1fr) là lỗi phổ biến — minimum min-content chặn track shrink và phá layout responsive. Ưu tiên minmax(0, 1fr) hoặc minimum pixel cụ thể khi cần shrink.


Alignment: Từ vựng chung, ngữ cảnh khác

CSS Box Alignment Level 3 thống nhất alignment property giữa Flexbox và Grid. Tên trùng nhưng trục khác.

Alignment Flexbox

PropertyAxisScope
justify-contentMainAll items in container
align-itemsCrossAll items in container
align-selfCrossSingle item override
align-contentCrossWrapped lines (not items)

Alignment Grid

PropertyAxisScope
justify-itemsInline (column)All items in their cells
align-itemsBlock (row)All items in their cells
justify-self / align-selfPer-axisSingle item in its cell
justify-contentInlineEntire grid within container (when grid < container)
align-contentBlockEntire grid within container
/* Center a grid that is smaller than its container */
.modal-grid {
  display: grid;
  grid-template-columns: repeat(3, 120px);
  justify-content: center; /* centers the grid tracks as a group */
  align-content: center;
  min-height: 400px;
}

Cái bẫy: justify-content: center trên flex container căn item dọc trục chính. Trên grid container, nó căn bộ track khi tổng kích thước track < container. Cùng tên property, chủ thể khác.


gap: Property cả hai đều có

gap (và row-gap / column-gap) hoạt động giống nhau trong Flexbox và Grid. Tạo khoảng cách giữa item mà không ảnh hưởng cạnh.

/* Prefer gap over margin hacks on first/last child */
.card-row {
  display: flex;
  gap: 1rem;
}

.photo-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1.5rem 1rem; /* row-gap column-gap */
}

Trước khi gap hỗ trợ Flexbox, ta dùng margin âm trên container và margin dương trên children. Pattern đó đã lỗi thời — dùng gap.


Kết hợp Flexbox và Grid

Chúng không loại trừ nhau. Layout production thường lồng chúng:

<!-- Grid for page shell -->
<div class="app">
  <header class="app-header">…</header>
  <aside class="app-sidebar">…</aside>
  <main class="app-main">
    <!-- Flex for toolbar within main -->
    <div class="toolbar">
      <h1>Dashboard</h1>
      <div class="toolbar-actions">…</div>
    </div>
    <!-- Grid for card matrix -->
    <div class="card-grid">…</div>
  </main>
</div>
.app {
  display: grid;
  grid-template-columns: 240px 1fr;
  grid-template-rows: auto 1fr;
  min-height: 100dvh;
}

.toolbar {
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 1.5rem;
}

Luồng quyết định:

  1. Topology trang / section? → Grid với grid-template-areas hoặc track explicit
  2. Một hàng hoặc cột item? → Flexbox
  3. Cột bằng nhau wrap responsive? → Grid với repeat(auto-fit, minmax(...))
  4. Đẩy một item ra rìa (vd “Sign out” trong nav)? → Flexbox với margin-inline-start: auto trên item đó
  5. Căn giữa thứ có kích thước không biết trong box? → Flexbox hoặc Grid place-items: center

Các cái bẫy vẫn cắn

1. flex-basis vs width

Khi flex-directionrow, flex-basis map sang width; khi column, map sang height. Đặt width: 200px trên flex item direction column không đặt kích thước trục chính — flex-basis (hoặc height) mới đặt.

/* ❌ width ignored as main-axis size in column flex */
.sidebar { width: 240px; flex-direction: column; }

/* ✅ */
.sidebar { flex: 0 0 240px; } /* row direction */
.sidebar { flex: 0 0 240px; flex-direction: column; } /* basis → height */

2. Bẫy overflow min-width: auto

Flex item mặc định min-width: auto, nghĩa là không shrink dưới min-size content. Chuỗi dài không break, block <pre> rộng, hoặc flex con lồng gây overflow ngang.

.flex-child {
  min-width: 0; /* or overflow: hidden on the flex item */
}

Đây là bug Flexbox production #1 sau khi bỏ float. Cell Grid cũng mặc định tương tự — áp min-width: 0 trên grid item cần truncate.

3. Chiều cao phần trăm cần parent xác định

height: 100% chỉ hoạt động khi mọi ancestor trên chuỗi có height explicit. Cách hiện đại: min-height: 100dvh trên page grid và hàng 1fr thay cho height phần trăm.

/* ❌ fragile */
.page { height: 100%; }
.main  { height: 100%; }

/* ✅ */
.page {
  display: grid;
  grid-template-rows: auto 1fr auto;
  min-height: 100dvh;
}

4. align-items: center chặn stretch — và collapse

Khi flex item nên lấp cross-axis (vd card cùng chiều cao trong hàng), align-items: stretch (mặc định) là đúng. Đặt center khiến item shrink-wrap chiều cao content.

5. Grid subgrid cho alignment lồng

Khi card grid nằm trong hàng grid và bạn cần line cột căn giữa các hàng card, subgrid (giờ hỗ trợ rộng) cho grid lồng kế thừa track definition của parent:

.card {
  display: grid;
  grid-row: span 1;
  grid-template-rows: subgrid;
  grid-row: span 3; /* title, body, footer align across cards */
}

Tra nhanh: Bản đồ property

Mục tiêuFlexboxGrid
Cột bằng nhau, wrap responsiveAwkward — use Gridrepeat(auto-fit, minmax(X, 1fr))
Khoảng cách giữa item một dòngjustify-content: space-betweenN/A (use gap or track sizing)
Đẩy item cuối ra rìamargin-inline-start: autojustify-self: end
Page shell full-heightPossible but fragilegrid-template-rows: auto 1fr auto + min-height: 100dvh
Căn giữa kích thước không biếtjustify-content + align-items: centerplace-items: center
Sidebar cố định + main fluidflex: 0 0 240px + flex: 1grid-template-columns: 240px 1fr

Lời kết

Flexbox trả lời “mọi thứ chảy thế nào?”. Grid trả lời “mỗi thứ thuộc về đâu?”. Nắm trục, tôn trọng intrinsic sizing, và chọn min-width: 0 trước khi chọn overflow-x: hidden trên body.

Playground trên là cách nhanh nhất để thấm mapping từ control sang CSS rule. Chuyển mode, cố tình phá layout, và chú ý trục nào mỗi property thực sự di chuyển — đó là cách mental model ăn sâu.