Learn CSS Grid Layout in-depth: defining explicit grids with grid-template, the fr unit, repeat() and minmax(), the responsive auto-fit / auto-fill pattern, line-based placement with span, and named template areas.

This lesson is currently only available in Vietnamese. Please switch the language toggle in the menu to Vietnamese to read the full guide and take the interactive quiz.

Bài 2, chúng ta đã làm chủ Flexbox — công cụ dàn trang một chiều (theo một trục: hàng hoặc cột). Nhưng khi cần một bố cục thực sự hai chiều — vừa kiểm soát hàng vừa kiểm soát cột cùng lúc, ví dụ layout cả trang web — thì CSS Grid mới là công cụ sinh ra cho việc đó.

CSS Grid Layout là hệ thống bố cục mạnh nhất mà CSS từng có: bạn vẽ ra một mạng lưới các đường (grid lines) tạo thành hàng và cột, rồi đặt từng phần tử con vào các ô (cell) hoặc vùng (area) tùy ý — kể cả cho chúng đè lên nhau hay nhảy cóc qua nhiều ô.

1. Grid Container & Grid Items — tư duy hai chiều

Mọi thứ bắt đầu khi bạn khai báo display: grid (hoặc inline-grid) trên phần tử cha — nó trở thành Grid Container, và mọi con trực tiếp của nó tự động trở thành Grid Items. Khác biệt cốt lõi so với Flexbox:

  • Flexbox dàn trang theo nội dung (content-first), một chiều mỗi lúc.
  • Grid dàn trang theo bố cục (layout-first), hai chiều cùng lúc: bạn định nghĩa cấu trúc lưới trước, rồi xếp nội dung vào.

Grid dùng hệ thống đường lưới (grid lines) được đánh số bắt đầu từ 1. Một lưới có N cột sẽ có N + 1 đường dọc; tương tự với hàng. Mọi cách đặt vị trí về sau đều dựa trên các đường này.

2. Định nghĩa lưới tường minh: grid-template & đơn vị fr

Hai thuộc tính trung tâm đặt trên container là grid-template-columnsgrid-template-rows. Chúng liệt kê kích thước của từng rãnh (track):

grid-template.css
.container {
  display: grid;
  /* 3 cột: 200px cố định | phần còn lại | 100px cố định */
  grid-template-columns: 200px 1fr 100px;
  /* 2 hàng, mỗi hàng cao tối thiểu theo nội dung */
  grid-template-rows: auto auto;
  gap: 16px; /* khoảng cách giữa các rãnh */
}

Ngôi sao của Grid là đơn vị fr (fraction — phần phân số của không gian còn trống). Sau khi trừ đi các rãnh có kích thước cố định và khoảng gap, không gian còn lại được chia cho các rãnh fr theo tỷ lệ:

$$\text{Free Space} = \text{Container Size} - \sum \text{fixed tracks} - \sum \text{gaps}$$ Một rãnh có hệ số fr nhận được phần không gian: $$\text{Track Size}_i = \text{Free Space} \times \frac{\text{fr}_i}{\sum \text{fr}}$$

Ví dụ grid-template-columns: 1fr 2fr 1fr chia không gian trống thành 4 phần: cột giữa rộng gấp đôi hai cột bên (2/4 so với 1/4).

ℹ️ Lưu ý: fr khác phần trăm (%) như thế nào?
% tính theo toàn bộ kích thước container và không tự trừ đi gap, nên repeat(3, 33.33%) kèm gap sẽ bị tràn. Còn fr chia không gian còn lại sau khi đã trừ gap, nên repeat(3, 1fr) luôn vừa khít. Hãy ưu tiên fr cho lưới co giãn.

Khi có nhiều rãnh lặp lại, dùng hàm repeat() cho gọn: grid-template-columns: repeat(12, 1fr) tạo ngay một lưới 12 cột kinh điển.

3. Lưới linh hoạt: minmax(), auto-fit & auto-fill

Sức mạnh responsive thật sự của Grid đến từ bộ ba repeat() + auto-fit/auto-fill + minmax(). Mẫu dưới đây tạo lưới thẻ tự động xuống dòng mà không cần một media query nào:

responsive-grid.css
.cards {
  display: grid;
  /* Mỗi thẻ rộng tối thiểu 220px, tối đa 1fr.
     Số cột tự động tính theo bề rộng container. */
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 20px;
}

minmax(220px, 1fr) nói: mỗi rãnh không bao giờ nhỏ hơn 220px nhưng có thể giãn tới 1fr. Khi container hẹp lại, số cột vừa được tự giảm; khi rộng ra, số cột tự tăng.

🕳️ Cạm bẫy: auto-fit và auto-fill cho kết quả khác nhau
Hai từ khóa trông giống nhau nhưng xử lý rãnh trống khác hẳn: auto-fill giữ lại các rãnh rỗng ở cuối (các thẻ co về kích thước min, để chừa chỗ trống), còn auto-fit thu gọn các rãnh rỗng về 0 (các thẻ giãn ra lấp đầy hàng). Nếu một hàng chỉ có 2 thẻ mà bạn muốn chúng giãn full chiều ngang → dùng auto-fit. Muốn giữ kích thước cố định và để khoảng trống → dùng auto-fill.

4. Sân chơi tương tác: Grid Sandbox

Hãy tự điều chỉnh số cột, khoảng cách và cách căn lề bên dưới, rồi chọn một ô để cho nó nhảy cóc qua nhiều cột/hàng (span) và xem lưới tự sắp xếp lại trong thời gian thực:

🎨 Sân chơi tương tác: Grid Sandbox

Container Controls

Item Configurator

1
2
3
4
5
6
grid.html
<div class="container">
  <div class="item item-1">Item 1</div>
  <div class="item item-2">Item 2</div>
  <div class="item item-3">Item 3</div>
  <div class="item item-4">Item 4</div>
  <div class="item item-5">Item 5</div>
  <div class="item item-6">Item 6</div>
</div>
grid.css

5. Đặt vị trí item theo đường lưới: grid-column & grid-row

Mặc định Grid tự xếp item lần lượt vào từng ô (auto-placement). Nhưng bạn có thể chỉ định chính xác một item bắt đầu và kết thúc ở đường lưới nào bằng grid-columngrid-row:

placement.css
.hero {
  /* Bắt đầu ở đường cột 1, kết thúc ở đường cột 3 → chiếm 2 cột */
  grid-column: 1 / 3;
  /* Tương đương cách viết bằng span: */
  /* grid-column: 1 / span 2; */
  grid-row: 1 / 2;
}
.sidebar {
  grid-column: 3 / 4; /* cột cuối */
  grid-row: 1 / 3; /* kéo dài qua 2 hàng */
}

Cú pháp start / end nhận số đường lưới; số âm đếm ngược từ cạnh cuối (-1 là đường cuối cùng), nên grid-column: 1 / -1 nghĩa là "chiếm trọn chiều ngang lưới" — rất hay dùng cho header.

🔬 Đào sâu: Lưới tường minh vs. lưới ngầm (implicit grid)
Khi bạn đặt một item ra ngoài phạm vi lưới đã khai báo (hoặc có nhiều item hơn số ô tường minh), trình duyệt tự tạo thêm các rãnh ngầm. Kích thước của chúng do grid-auto-rows / grid-auto-columns quyết định, và hướng dòng chảy do grid-auto-flow: row | column | dense điều khiển. Giá trị dense cho phép Grid "lấp lỗ hổng" bằng cách kéo item nhỏ phía sau lên ô trống phía trước — tiện cho gallery nhưng có thể làm sai thứ tự đọc (ảnh hưởng accessibility).

6. Bố cục bằng tên vùng: grid-template-areas

Cách trực quan nhất để dựng layout cả trang là vẽ nó bằng chữ. grid-template-areas cho phép đặt tên cho từng vùng rồi "vẽ" sơ đồ bố cục như một bức tranh ASCII:

areas.css
.layout {
  display: grid;
  grid-template-columns: 200px 1fr;
  grid-template-rows: auto 1fr auto;
  grid-template-areas:
    "header  header"
    "sidebar main"
    "footer  footer";
  min-height: 100vh;
  gap: 12px;
}
.layout > header  { grid-area: header; }
.layout > nav     { grid-area: sidebar; }
.layout > main    { grid-area: main; }
.layout > footer  { grid-area: footer; }
💡 Mẹo: Đổi cả layout chỉ bằng một khối areas
Vì bố cục được mô tả tập trung trong grid-template-areas, bạn có thể sắp xếp lại toàn bộ trang cho mobile chỉ bằng cách viết lại sơ đồ vùng trong một media query (ví dụ dồn sidebar xuống dưới main) — không cần đụng tới HTML hay đổi thứ tự thẻ.

Grid và Flexbox không loại trừ nhau — thực tế bạn thường dùng Grid cho bố cục tổng thể của trang và Flexbox để căn chỉnh nội dung bên trong từng ô. Toán ma trận đằng sau các phép sắp đặt không gian này còn được khai thác sâu hơn ở series WebGL — Toán học & Hệ tọa độ.

Trắc nghiệm ôn tập

Câu 1: Với grid-template-columns: 1fr 2fr 1fr và không có rãnh cố định nào khác, cột giữa chiếm bao nhiêu phần không gian trống?

Trắc nghiệm ôn tập

Câu 2: Bạn muốn các thẻ giãn ra lấp đầy hết chiều ngang khi một hàng còn dư chỗ. Nên dùng từ khóa nào trong repeat()?

Trắc nghiệm ôn tập

Câu 3: grid-column: 1 / -1 có ý nghĩa gì?

📖 Tài liệu tham khảo / References

Related Articles

Bài viết liên quan trong series

Lesson 4: Transition & Easing — The Art of Smooth Change Bài 4: Transition & Easing — Nghệ Thuật Của Sự Chuyển Tiếp Lesson 2: Master Flexbox — One-Dimensional Layout Bài 2: Flexbox Toàn Tập — Dàn Trang Một Chiều Tối Ưu Back to CSS Course Overview Quay lại Lộ trình Series CSS