Nếu Flexbox là để sắp xếp items theo một chiều (ngang hoặc dọc), thì CSS Grid là để sắp xếp theo hai chiều (ngang và dọc cùng lúc). Grid rất mạnh mẽ và thích hợp cho những bố cục phức tạp như dashboard, trang báo, hoặc lưới sản phẩm.
Bài này sẽ dạy bạn cách sử dụng CSS Grid để tạo những bố cục đẹp mắt và responsive dễ dàng.
CSS Grid là gì?
CSS Grid là hệ thống bố cục 2 chiều cho phép bạn chia trang web thành hàng (rows) và cột (columns), rồi sắp xếp các phần tử vào các ô cụ thể.
- 📊 Grid Container: Phần tử cha (có
display: grid;) - 📦 Grid Items: Phần tử con (những cái được sắp xếp)
- 📐 Grid Lines: Các đường chia (hàng và cột)
- ⬜ Grid Cells: Các ô riêng lẻ
- 🔲 Grid Track: Một hàng hoặc cột
┌─────────────────────────────────────────┐
│ Grid Container (display: grid) │
│ ┌────────────┬────────────┬─────────┐ │
│ │ Cell 1 │ Cell 2 │ Cell 3 │ │
│ │ (Item 1) │ (Item 2) │ (Item 3)│ │
│ ├────────────┼────────────┼─────────┤ │
│ │ Cell 4 │ Cell 5 │ Cell 6 │ │
│ │ (Item 4) │ (Item 5) │ (Item 6)│ │
│ └────────────┴────────────┴─────────┘ │
└─────────────────────────────────────────┘Bước 1: Tạo Grid Container – display: grid
Để sử dụng Grid, thêm display: grid; vào phần tử cha.
.container {
display: grid; /* Bật Grid */
}Grid-template-columns – Định nghĩa cột
grid-template-columns xác định số cột và kích thước của chúng.
/* 3 cột bằng nhau (mỗi cột 1fr) */
.container {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
}
/* Hoặc dùng repeat() */
.container {
display: grid;
grid-template-columns: repeat(3, 1fr);
}
/* Cột có kích thước khác nhau */
.container {
display: grid;
grid-template-columns: 200px 1fr 300px;
/* Cột 1: 200px cố định, Cột 2: 1fr (phần còn lại), Cột 3: 300px */
}
/* Kết hợp các đơn vị */
.container {
display: grid;
grid-template-columns: 100px 2fr 1fr;
/* Cột 2 rộng gấp đôi cột 3 */
}Các đơn vị trong Grid
| Đơn vị | Ý nghĩa | Ví dụ |
|---|---|---|
px | Pixel (cố định) | grid-template-columns: 200px 300px; |
fr | Fraction (phân chia không gian còn lại) | grid-template-columns: 1fr 2fr; (tỷ lệ 1:2) |
% | Phần trăm | grid-template-columns: 25% 75%; |
auto | Tự động theo nội dung | grid-template-columns: auto 1fr; |
minmax() | Kích thước tối thiểu và tối đa | grid-template-columns: minmax(200px, 1fr); |
/* fr: phân chia không gian linh hoạt */
.three-columns {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
/* 3 cột bằng nhau, chia đều chiều rộng container */
}
/* Kết hợp px và fr */
.sidebar-layout {
display: grid;
grid-template-columns: 250px 1fr;
/* Sidebar: 250px cố định, Main: phần còn lại */
}
/* repeat() để viết gọn */
.grid-12 {
display: grid;
grid-template-columns: repeat(12, 1fr);
/* 12 cột bằng nhau (giống Bootstrap) */
}
/* auto-fit: tự động số cột dựa trên kích thước */
.responsive-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
/* Nếu có chỗ → nhiều cột, hẹp → ít cột, mỗi cột ít nhất 200px */
}Grid-template-rows – Định nghĩa hàng
grid-template-rows xác định số hàng và kích thước của chúng. Cách dùng giống như columns.
/* 2 hàng cố định */
.container {
display: grid;
grid-template-columns: 1fr 1fr;
grid-template-rows: 100px 200px;
/* Hàng 1: 100px, Hàng 2: 200px */
}
/* 3 hàng bằng nhau */
.container {
display: grid;
grid-template-rows: repeat(3, 1fr);
}
/* Cao tối thiểu (auto) */
.container {
display: grid;
grid-template-rows: auto auto auto;
/* Hàng tự động theo nội dung */
}Grid-gap – Khoảng cách giữa cells
grid-gap tạo khoảng cách giữa các rows và columns.
/* Khoảng cách như nhau */
.container {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 20px; /* Khoảng cách 20px theo cả hàng và cột */
}
/* Hoặc riêng từng hướng */
.container {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
row-gap: 20px; /* Khoảng cách giữa các hàng */
column-gap: 30px; /* Khoảng cách giữa các cột */
}Grid-column và Grid-row – Kéo dãn cells
Một item có thể chiếm nhiều cột hoặc hàng bằng cách dùng grid-column và grid-row.
/* Item chiếm 2 cột */
.item1 {
grid-column: 1 / 3; /* Từ cột 1 đến cột 3 (tính từ 1) */
/* Hoặc: grid-column: span 2; (chiếm 2 cột) */
}
/* Item chiếm 2 hàng */
.item2 {
grid-row: 1 / 3; /* Từ hàng 1 đến hàng 3 */
/* Hoặc: grid-row: span 2; (chiếm 2 hàng) */
}
/* Item chiếm 2 hàng và 2 cột */
.item3 {
grid-column: span 2;
grid-row: span 2;
}Ví dụ hoàn chỉnh – Tạo dashboard với Grid
<!DOCTYPE html>
<html lang="vi">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CSS Grid Dashboard</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div class="dashboard">
<!-- Header -->
<header class="header">
<h1>Dashboard</h1>
<p>Chào mừng bạn quay lại!</p>
</header>
<!-- Sidebar -->
<aside class="sidebar">
<nav>
<a href="#" class="nav-item active">Trang chủ</a>
<a href="#" class="nav-item">Báo cáo</a>
<a href="#" class="nav-item">Phân tích</a>
<a href="#" class="nav-item">Cài đặt</a>
</nav>
</aside>
<!-- Main content -->
<main class="main">
<!-- Card 1: Doanh thu -->
<div class="card">
<h3>💰 Doanh thu</h3>
<p class="number">$25,000</p>
<p class="subtitle">Tháng này</p>
</div>
<!-- Card 2: Người dùng -->
<div class="card">
<h3>👥 Người dùng</h3>
<p class="number">1,250</p>
<p class="subtitle">Hoạt động</p>
</div>
<!-- Card 3: Đơn hàng -->
<div class="card">
<h3>📦 Đơn hàng</h3>
<p class="number">342</p>
<p class="subtitle">Đã xử lý</p>
</div>
<!-- Card 4: Đánh giá -->
<div class="card">
<h3>⭐ Đánh giá</h3>
<p class="number">4.8</p>
<p class="subtitle">Trung bình</p>
</div>
<!-- Chart (chiếm 2 cột) -->
<div class="chart">
<h3>Doanh thu theo tháng</h3>
<div style="height: 300px; background: linear-gradient(to top, #3498db, #ecf0f1); border-radius: 8px;"></div>
</div>
<!-- Table (chiếm 2 cột) -->
<div class="table-container">
<h3>Đơn hàng gần đây</h3>
<table>
<tr>
<th>ID</th>
<th>Khách hàng</th>
<th>Tổng tiền</th>
<th>Trạng thái</th>
</tr>
<tr>
<td>#001</td>
<td>Nguyễn Văn A</td>
<td>$500</td>
<td><span class="badge">Hoàn thành</span></td>
</tr>
<tr>
<td>#002</td>
<td>Trần Thị B</td>
<td>$300</td>
<td><span class="badge pending">Chờ xử lý</span></td>
</tr>
</table>
</div>
</main>
<!-- Footer -->
<footer class="footer">
<p>© 2024 Dashboard. Tất cả quyền được bảo lưu.</p>
</footer>
</div>
</body>
</html>/* FILE: styles.css */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: Arial, sans-serif;
background-color: #ecf0f1;
color: #333;
}
/* Dashboard grid layout */
.dashboard {
display: grid;
grid-template-columns: 250px 1fr;
grid-template-rows: 80px 1fr 60px;
min-height: 100vh;
gap: 20px;
padding: 20px;
}
/* Header - chiếm tất cả chiều rộng */
.header {
grid-column: 1 / -1; /* -1 = cột cuối cùng */
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 20px 30px;
border-radius: 8px;
display: flex;
justify-content: space-between;
align-items: center;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.header h1 {
font-size: 28px;
}
.header p {
font-size: 14px;
opacity: 0.9;
}
/* Sidebar */
.sidebar {
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
height: fit-content;
}
.sidebar nav {
display: flex;
flex-direction: column;
gap: 10px;
}
.nav-item {
display: block;
padding: 12px 15px;
text-decoration: none;
color: #333;
border-radius: 5px;
transition: all 0.3s;
}
.nav-item:hover,
.nav-item.active {
background-color: #667eea;
color: white;
}
/* Main content grid */
.main {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 20px;
}
/* Cards */
.card {
background: white;
padding: 25px;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
transition: transform 0.3s;
}
.card:hover {
transform: translateY(-5px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.card h3 {
font-size: 16px;
color: #666;
margin-bottom: 15px;
}
.card .number {
font-size: 32px;
font-weight: bold;
color: #2c3e50;
margin-bottom: 10px;
}
.card .subtitle {
font-size: 12px;
color: #95a5a6;
}
/* Chart - chiếm 2 cột */
.chart {
grid-column: span 2;
background: white;
padding: 25px;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.chart h3 {
margin-bottom: 20px;
color: #2c3e50;
}
/* Table - chiếm 2 cột */
.table-container {
grid-column: span 2;
background: white;
padding: 25px;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.table-container h3 {
margin-bottom: 20px;
color: #2c3e50;
}
.table-container table {
width: 100%;
border-collapse: collapse;
}
.table-container th {
background-color: #f9f9f9;
padding: 12px;
text-align: left;
font-weight: bold;
border-bottom: 2px solid #ecf0f1;
}
.table-container td {
padding: 12px;
border-bottom: 1px solid #ecf0f1;
}
.table-container tr:hover {
background-color: #f9f9f9;
}
.badge {
display: inline-block;
padding: 5px 12px;
background-color: #27ae60;
color: white;
border-radius: 20px;
font-size: 12px;
}
.badge.pending {
background-color: #f39c12;
}
/* Footer - chiếm tất cả chiều rộng */
.footer {
grid-column: 1 / -1;
background-color: #2c3e50;
color: white;
display: flex;
justify-content: center;
align-items: center;
border-radius: 8px;
}
/* Responsive */
@media (max-width: 1024px) {
.dashboard {
grid-template-columns: 1fr;
}
.sidebar {
grid-column: 1;
display: flex;
}
.sidebar nav {
flex-direction: row;
gap: 10px;
flex-wrap: wrap;
}
.main {
grid-template-columns: repeat(2, 1fr);
}
.chart,
.table-container {
grid-column: span 2;
}
}
@media (max-width: 768px) {
.main {
grid-template-columns: 1fr;
}
.card,
.chart,
.table-container {
grid-column: span 1;
}
.header {
flex-direction: column;
gap: 10px;
}
}Grid-auto-flow – Tự động sắp xếp items
grid-auto-flow xác định cách items được sắp xếp tự động khi không có vị trí rõ ràng.
| Giá trị | Mô tả |
|---|---|
row (mặc định) | Sắp xếp theo hàng (trái sang phải, dưới xuống dưới) |
column | Sắp xếp theo cột (trên xuống dưới, trái sang phải) |
dense | Lấp đầy các khoảng trống (có thể sắp xếp không theo thứ tự) |
/* Sắp xếp theo hàng */
.grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-auto-flow: row; /* Mặc định */
}
/* Sắp xếp theo cột */
.grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-auto-flow: column;
}Tóm tắt các thuộc tính Grid quan trọng
| Thuộc tính | Áp dụng cho | Chức năng |
|---|---|---|
display: grid | Container | Bật Grid |
grid-template-columns | Container | Định nghĩa số cột và kích thước |
grid-template-rows | Container | Định nghĩa số hàng và kích thước |
gap | Container | Khoảng cách giữa rows và columns |
grid-column | Item | Chiếm mấy cột (span) |
grid-row | Item | Chiếm mấy hàng (span) |
grid-auto-flow | Container | Cách sắp xếp tự động (row/column) |
justify-items | Container | Canh chữa ngang (horizontal) |
align-items | Container | Canh chữa dọc (vertical) |
Grid vs Flexbox – Khi nào dùng cái nào?
| Tiêu chí | Flexbox | Grid |
|---|---|---|
| Số chiều | 1 chiều (hàng hoặc cột) | 2 chiều (hàng và cột) |
| Trường hợp sử dụng | Navigation, buttons, danh sách | Dashboard, lưới sản phẩm, layout trang |
| Độ phức tạp | Đơn giản, dễ học | Mạnh mẽ, linh hoạt hơn |
| Responsive | Dễ với flex-wrap | Dễ với grid-template-columns: repeat(auto-fit) |
/* ✅ Dùng Flexbox: Navigation bar */
.navbar {
display: flex;
justify-content: space-between;
align-items: center;
}
/* ✅ Dùng Grid: Dashboard layout */
.dashboard {
display: grid;
grid-template-columns: 250px 1fr;
grid-template-rows: 80px 1fr 60px;
}
/* ✅ Dùng Grid: Product grid */
.products {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 20px;
}Tóm tắt
- ✅ CSS Grid: Bố cục 2 chiều (rows và columns)
- ✅ display: grid: Bật Grid
- ✅ grid-template-columns: Định nghĩa số cột
- ✅ grid-template-rows: Định nghĩa số hàng
- ✅ gap: Khoảng cách giữa cells
- ✅ grid-column: Item chiếm mấy cột
- ✅ grid-row: Item chiếm mấy hàng
- ✅ fr: Unit linh hoạt (fraction)
- ✅ repeat(): Viết gọn các cột/hàng
- ✅ auto-fit/auto-fill: Tự động số cột dựa trên kích thước
- ✅ Flexbox vs Grid: 1 chiều vs 2 chiều
🎯 Bài tập thực hành
Bài tập 1: Tạo grid lưới sản phẩm 4 cột:
- Dùng
grid-template-columns: repeat(4, 1fr) - Gap: 20px
- Mỗi item có ảnh (height 250px), tiêu đề, giá
- Responsive: 2 cột trên tablet (768px), 1 cột trên mobile (480px)
- Item đầu tiên chiếm 2 cột × 2 hàng (lớn hơn)
Bài tập 2: Tạo layout trang tin tức (giống báo):
- Header (chiếm toàn chiều rộng): 80px
- Content (3 cột): Main (2 cột) + Sidebar (1 cột)
- Footer (chiếm toàn chiều rộng)
- Main có 3 bài viết, mỗi bài ảnh + tiêu đề + mô tả
- Responsive: 1 cột trên mobile
Bài tập 3: Tạo dashboard phức tạp:
- Cấu trúc: Header + Sidebar + Main + Footer (giống ví dụ trong bài)
- Main: 4 cards (mỗi 1 cột), 1 chart (2 cột), 1 table (2 cột)
- Cards: có icon, số, subtitle (doanh thu, người dùng, đơn hàng, đánh giá)
- Responsive: tablet (2 cột), mobile (1 cột)
Chúc bạn thành công! 🚀