Mở đầu — vì sao bài này quan trọng
Ở Bài 21 bạn đã làm chủ Flexbox — công cụ tuyệt vời để xếp các phần tử theo một chiều, hoặc một hàng ngang, hoặc một cột dọc. Nhưng cuộc sống thật của một trang web không phẳng như vậy. Một trang dashboard điển hình có header chạy ngang trên cùng, sidebar dọc bên trái, vùng nội dung chính ở giữa, và footer dưới đáy. Đó là bố cục hai chiều — vừa có hàng, vừa có cột, và chúng phải khớp với nhau như những viên gạch.
Nếu cố ép một layout hai chiều vào Flexbox, bạn sẽ rơi vào cái bẫy quen thuộc: lồng hết div này vào div khác, chồng nhiều lớp display: flex, rồi vật lộn với width: 70% cộng margin để mọi thứ "tạm khớp" trên màn hình của bạn — để rồi vỡ tan trên màn hình của người khác.
CSS Grid sinh ra để giải quyết đúng vấn đề đó. Nó cho phép bạn vẽ ra một lưới thật sự gồm hàng và cột, rồi đặt từng phần tử vào ô mong muốn. Đây là một trong những công cụ layout mạnh nhất mà CSS từng có, và sau bài này bạn sẽ không bao giờ nhìn một trang web theo cách cũ nữa. Hãy coi Grid là khung xương của toàn trang, còn Flexbox là cách bạn căn chỉnh nội dung bên trong từng bộ phận.
Khái niệm cốt lõi
Flexbox vs Grid — phân vai rõ ràng
Trước khi đi sâu, hãy gỡ nút thắt khiến nhiều bạn mới học bối rối: "Học Grid rồi có bỏ Flexbox không?". Câu trả lời là không — chúng là đồng đội, không phải đối thủ.
- Flexbox là 1 chiều (one-dimensional). Bạn cho nó một danh sách phần tử, nó xếp theo một trục — ngang hoặc dọc. Phù hợp khi nội dung quyết định kích thước: một thanh menu, một nhóm nút, các tag trong một bài viết.
- Grid là 2 chiều (two-dimensional). Bạn định nghĩa trước cả hàng lẫn cột, tạo thành một lưới, rồi đặt phần tử vào. Phù hợp khi bố cục quyết định kích thước: layout tổng thể của trang, một lưới sản phẩm, một bảng giá.
Thiết lập một Grid container
Mọi thứ bắt đầu bằng việc biến một phần tử cha thành lưới:
.container {
display: grid;
grid-template-columns: 200px 1fr 1fr;
grid-template-rows: auto 1fr auto;
gap: 16px;
}
grid-template-columnsđịnh nghĩa các cột: ở đây là 3 cột — cột đầu rộng cố định 200px, hai cột sau chia đều phần còn lại.grid-template-rowsđịnh nghĩa các hàng.gaplà khoảng cách giữa các ô — thay thế hoàn toàn cho thủ thuậtmarginrườm rà ngày xưa.
Đơn vị fr — ngôi sao của Grid
fr (fraction — phần) là đơn vị riêng của Grid, biểu thị một phần của không gian còn trống. Nếu bạn viết grid-template-columns: 1fr 2fr 1fr, không gian trống được chia thành 4 phần: cột giữa chiếm 2 phần, hai cột bên mỗi cột 1 phần. Cái hay là fr tự động tính toán sau khi đã trừ đi các phần cố định và gap, nên bạn không bao giờ phải làm phép tính phần trăm thủ công nữa.
repeat() và minmax() — bộ đôi quyền lực
Thay vì gõ 1fr 1fr 1fr 1fr, bạn viết gọn:
grid-template-columns: repeat(4, 1fr);
Và đây là dòng code "thần thánh" tạo lưới responsive mà không cần một media query nào:
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
Dịch ra tiếng Việt: "Hãy nhồi càng nhiều cột càng tốt, mỗi cột rộng tối thiểu 240px và tối đa 1fr". Trên màn hình rộng, lưới tự xếp 4-5 cột; trên điện thoại, nó tự co còn 1 cột. Một dòng, thay cho cả chục dòng media query — bạn sẽ thấy sức mạnh thật của nó trong phần Tình huống thực tế.
Đặt phần tử vào lưới
Mặc định các phần tử con tự lấp đầy lưới theo thứ tự. Nhưng bạn có thể chỉ định chính xác vị trí:
.sidebar {
grid-column: 1 / 2; / từ đường kẻ dọc 1 đến 2 /
grid-row: 2 / 3;
}
.main {
grid-column: 2 / 4; / trải dài 2 cột /
}
Lưu ý quan trọng: Grid đếm theo đường kẻ (grid line), không phải theo ô. Lưới 3 cột có 4 đường kẻ dọc (đánh số 1, 2, 3, 4). grid-column: 1 / 4 nghĩa là trải từ mép trái đến mép phải.
grid-template-areas — đặt tên cho vùng
Đây là tính năng khiến CSS Grid trở nên gần như "biết đọc". Bạn vẽ layout bằng tên:
.layout {
display: grid;
grid-template-columns: 240px 1fr;
grid-template-rows: auto 1fr auto;
grid-template-areas:
"header header"
"sidebar main"
"footer footer";
min-height: 100vh;
}
.header { grid-area: header; }
.sidebar { grid-area: sidebar; }
.main { grid-area: main; }
.footer { grid-area: footer; }
Nhìn vào đoạn grid-template-areas, bạn thấy được hình dạng trang ngay trong code: header trải hết chiều ngang, sidebar và main nằm cạnh nhau, footer trải hết chiều ngang. Đây là cách viết layout dễ đọc và dễ bảo trì nhất mà CSS có.
Tình huống thực tế
Tình huống 1 — Trang sản phẩm của Tiki và lưới tự co
Hãy tưởng tượng đội frontend của một sàn thương mại điện tử kiểu Tiki cần dựng lưới sản phẩm cho trang danh mục. Yêu cầu: trên laptop hiển thị 5 cột, trên tablet 3 cột, trên điện thoại 2 cột, và mỗi thẻ sản phẩm không bao giờ hẹp dưới 180px (vì dưới mức đó ảnh và giá sẽ vỡ).
Cách làm cũ với Flexbox: bạn phải đặt flex-basis, rồi viết 3 media query cho 3 breakpoint, rồi căn gap thủ công. Khoảng 30-40 dòng CSS, và mỗi lần marketing muốn đổi từ 5 sang 6 cột là một lần phải sửa nhiều chỗ.
Cách làm với Grid gọn đến mức đáng kinh ngạc:
.product-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
gap: 16px;
}
Bốn dòng. Không một media query nào. Lưới tự tính: màn hình 1440px chứa được khoảng 7 cột, màn hình 768px chứa 4 cột, điện thoại 360px chứa 2 cột — tất cả tự động. Khi sàn có đợt sale và muốn thẻ to hơn, designer chỉ cần đổi 180px thành 220px ở một dòng duy nhất.
Bài học rút ra: với bất kỳ lưới card responsive nào — sản phẩm, bài viết, hồ sơ nhân sự — repeat(auto-fill, minmax(...)) gần như luôn là câu trả lời. Nó là "viên đạn bạc" của Grid.
Tình huống 2 — Dashboard nội bộ của một startup fintech ở TP.HCM
Một startup fintech tại Quận 1 xây dashboard cho nhân viên vận hành. Layout kinh điển: header cố định trên cùng (chiều cao 64px), sidebar điều hướng bên trái (rộng 260px), vùng nội dung chính, và một footer nhỏ. Bạn đọc kỹ tên này hẳn thấy quen — đây chính là layout 2 chiều mà Flexbox phải vật lộn còn Grid giải quyết trong vài dòng.
.app {
display: grid;
grid-template-columns: 260px 1fr;
grid-template-rows: 64px 1fr 40px;
grid-template-areas:
"header header"
"sidebar main"
"sidebar footer";
min-height: 100vh;
}
Lưu ý ở đây sidebar được kéo dài xuống cả hàng footer (xuất hiện hai lần ở cột trái), trong khi main và footer nằm ở cột phải. Khi đội product yêu cầu "ẩn sidebar khi người dùng thu gọn", lập trình viên chỉ cần đổi grid-template-areas trong một class khác và cho cột đầu về 0. Toàn bộ layout tự sắp xếp lại — không cần đụng đến từng phần tử con.
Bài học rút ra: với layout khung trang có nhiều vùng cố định, grid-template-areas cho bạn một bản đồ trực quan ngay trong CSS, giúp cả team đọc hiểu cấu trúc trong vài giây.
Tình huống 3 — Trang báo điện tử kiểu VnExpress và lưới 12 cột
Một tòa soạn báo điện tử cỡ VnExpress dựng trang chủ với một lưới 12 cột làm nền tảng (bạn sẽ học sâu về hệ lưới 12 cột ở Bài 11, ở đây ta chỉ nhìn góc kỹ thuật Grid). Tin chính chiếm 8 cột, cột tin phụ chiếm 4 cột; bên dưới là ba khối tin nhỏ mỗi khối 4 cột.
.news {
display: grid;
grid-template-columns: repeat(12, 1fr);
gap: 20px;
}
.featured { grid-column: span 8; }
.aside { grid-column: span 4; }
.card { grid-column: span 4; }
span 8 nghĩa là "trải rộng 8 cột" mà không cần ghi rõ bắt đầu từ đâu — Grid tự sắp. Khi biên tập viên muốn tin chính to hơn, đổi span 8 thành span 9 và cột phụ thành span 3. Layout cập nhật tức thì, các viên gạch vẫn khớp hoàn hảo vì tổng luôn bằng 12.
Bài học rút ra: hệ lưới 12 cột là tiêu chuẩn ngành xuất bản và thiết kế. Grid biến nó từ một quy ước trên giấy thành code thật, với cú pháp span cực kỳ linh hoạt.
Hướng dẫn từng bước
Hãy cùng dựng một layout dashboard hoàn chỉnh từ con số không.
Bước 1 — Phác họa lưới trên giấy trước. Trước khi gõ một dòng CSS, vẽ ra bạn cần bao nhiêu hàng, bao nhiêu cột. Ví dụ: 2 cột (sidebar + nội dung), 3 hàng (header, body, footer). Bước này quan trọng hơn bạn nghĩ — Grid thưởng cho người có kế hoạch.
Bước 2 — Dựng HTML phẳng. Khác với Flexbox hay phải lồng div, Grid khuyến khích cấu trúc phẳng:
<div class="app">
<header class="header">...</header>
<nav class="sidebar">...</nav>
<main class="main">...</main>
<footer class="footer">...</footer>
</div>
Bước 3 — Khai báo container. Thêm display: grid và định nghĩa cột/hàng:
.app {
display: grid;
grid-template-columns: 260px 1fr;
grid-template-rows: 64px 1fr 40px;
gap: 0;
min-height: 100vh;
}
Bước 4 — Vẽ vùng bằng grid-template-areas. Thêm bản đồ vùng như đã làm ở Tình huống 2, rồi gán grid-area cho từng phần tử con.
Bước 5 — Dùng Flexbox bên trong từng vùng. Bên trong .header, bạn muốn logo trái và avatar phải? Đó là việc của Flexbox:
.header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 24px;
}
Đây chính là minh họa sống động cho nguyên tắc "Grid dựng khung, Flexbox căn nội dung".
Bước 6 — Mở DevTools để kiểm tra. Trong Chrome/Firefox, mở DevTools, tìm phần tử có display: grid, bấm vào biểu tượng "grid" nhỏ bên cạnh. Trình duyệt sẽ vẽ chồng các đường kẻ và số thứ tự lên trang. Đây là công cụ debug Grid tốt nhất — luôn bật nó khi bạn lạc lối.
Lỗi thường gặp & mẹo
Lỗi 1 — Nhầm grid-template-columns với grid-template-rows. Rất nhiều bạn mới viết số cột vào chỗ hàng. Mẹo nhớ: columns = cột = chiều dọc của lưới (số phần tử trong dòng columns chính là số cột bạn thấy theo chiều ngang).
Lỗi 2 — Quên rằng Grid đếm theo đường kẻ chứ không phải ô. Lưới 3 cột có 4 đường kẻ. grid-column: 1 / 4 (không phải 1 / 3) mới là trải hết 3 cột. Khi vị trí phần tử "lệch một ô", 90% là do nhầm chỗ này.
Lỗi 3 — Lạm dụng px cho mọi cột. Nếu bạn viết grid-template-columns: 300px 600px 300px, layout sẽ tràn hoặc thừa trên màn hình khác kích thước. Hãy để ít nhất một cột dùng 1fr để Grid tự co giãn.
Lỗi 4 — Dùng margin thay vì gap. Thói quen cũ khiến nhiều người vẫn đặt margin để tách các ô, gây lệch ở mép. Grid có gap riêng — sạch sẽ và không bao giờ thừa margin ở viền ngoài.
Mẹo 1 — place-items: center để căn giữa thần tốc. Muốn căn giữa nội dung trong mọi ô theo cả hai chiều? Một dòng place-items: center trên container là xong. Đây là cách căn giữa ngắn gọn nhất CSS từng có.
Mẹo 2 — auto-fill vs auto-fit. Cả hai gần giống nhau. auto-fill giữ lại các cột trống (lưới giữ chỗ), còn auto-fit thu các cột trống lại và để nội dung giãn ra lấp đầy. Khi lưới có ít phần tử mà bạn muốn chúng dàn rộng, hãy chọn auto-fit.
Mẹo 3 — Kết hợp minmax để tránh vỡ. Luôn đặt giá trị tối thiểu hợp lý trong minmax(min, 1fr). Con số min chính là "lằn ranh đỏ" để nội dung không bị bóp méo trên điện thoại.
Bài tập thực hành
- Lưới sản phẩm tự co. Tạo một container chứa 9 thẻ sản phẩm (mỗi thẻ là một
divcó ảnh, tên, giá). Dùngrepeat(auto-fill, minmax(200px, 1fr))vàgap: 16px. Thu nhỏ cửa sổ trình duyệt và quan sát số cột tự thay đổi — không được viết bất kỳ media query nào.
- Khung dashboard hoàn chỉnh. Dựng layout header / sidebar / main / footer bằng
grid-template-areas. Sidebar rộng 240px, header cao 60px, main chiếm phần còn lại,min-height: 100vh. Sau đó dùng Flexbox bên trong header để đặt logo bên trái, nút đăng xuất bên phải.
- Lưới 12 cột. Tạo một lưới
repeat(12, 1fr). Đặt một khốifeaturedtrảispan 8, một khốiasidetrảispan 4, rồi ba thẻ con mỗi thẻspan 4ở hàng dưới. Thử đổifeaturedthànhspan 9vàasidethànhspan 3, kiểm tra xem layout còn khớp không.
- Thử thách nâng cao. Lấy bài tập 2, thêm một nút "Thu gọn sidebar". Viết một class
.collapsedđổigrid-template-columnstừ240px 1frthành0 1fr. Dùng JavaScript bật/tắt class này khi bấm nút và quan sát toàn layout tự sắp xếp lại mượt mà.
Tóm tắt
- Flexbox là 1 chiều, Grid là 2 chiều. Chúng là đồng đội: Grid dựng khung xương tổng thể của trang, Flexbox căn nội dung bên trong từng khối.
- Khởi tạo bằng
display: grid, rồi định nghĩa lưới vớigrid-template-columnsvàgrid-template-rows. frchia không gian còn trống theo tỷ lệ;repeat()viết gọn;minmax()đặt giới hạn an toàn.- Câu thần chú cho lưới card responsive:
repeat(auto-fill, minmax(Xpx, 1fr))— tạo lưới tự co mà không cần media query. grid-template-areascho bạn vẽ layout bằng tên, biến CSS thành một bản đồ dễ đọc — lý tưởng cho khung trang header/sidebar/main/footer.- Nhớ rằng Grid đếm theo đường kẻ, ưu tiên
gapthay chomargin, và luôn mở DevTools Grid inspector khi cần debug.