CSS Grid vs Flexbox: khi nào dùng Flex, khi nào dùng Grid (nav & dashboard)
Đừng hỏi “cái nào tốt hơn” — hỏi bạn đang thiết kế mấy chiều. Một trục (nav, chip, nhóm nút, căn icon+label) → Flexbox. Hai chiều (shell trang, dashboard widget, gallery căn hàng, form label/input thẳng cột) → Grid. Pattern thắng: Grid làm khung vùng, Flex làm ruột component. Prototype vùng khó trên CSS Grid generator rồi mới dán vào stylesheet.
Trên nhóm Discord / Slack của freelancer và team outsourcing Việt Nam, câu hỏi lặp lại mỗi sprint: “Navbar dùng Grid được không?”, “Dashboard toàn Flex bị vỡ cột — sao?”. Tài liệu hay dừng ở định nghĩa “1D vs 2D”. Bài này đi tiếp: quyết định theo màn hình thật (nav, card, dashboard), bảng litmus, code copy được, và lỗi DevTools hay gặp.
Ai nên đọc
- Frontend đang chọn layout cho landing, admin, SaaS dashboard
- Dev từng nhét cả trang vào
display: flexrồi sinh wrapper lồng nhau - Ai cần giải thích nhanh cho designer / reviewer: vì sao vùng này là Grid
Litmus test (30 giây)
- Nếu một item cao hơn, các hàng/cột khác chiều có phải căn lại như một hệ không?
- Có → nghiêng Grid
- Không → nghiêng Flex
- Bạn có đang vẽ trên giấy cả đường ngang và dọc để căn không?
- Có → Grid
- Chỉ một hàng hoặc một cột → Flex
| Bài toán | Ưu tiên | Vì sao |
|---|---|---|
| Header / main / sidebar / footer | Grid | Named areas giữ sanity khi đổi breakpoint |
| Navbar: logo + link + CTA | Flex | Một trục ngang, space-between / gap |
| Gallery card cao đều theo hàng | Grid | auto-fit + minmax; track chung |
| Chip / tag wrap xuống dòng | Flex | flex-wrap; không cần căn cột giữa hàng |
| Form label + input thẳng cột | Grid | Cột dùng chung; Flex từng hàng dễ lệch khi copy dài ngắn khác |
| Footer nút trong card | Flex | justify-content + align-items trên một hàng |
| Dashboard 12 cột widget | Grid | span cột; vùng độc lập với nội dung widget |
| Căn giữa icon + chữ trong nút | Flex | align-items: center là đủ |
Navbar: hầu như luôn Flex
.nav {
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
flex-wrap: wrap; /* menu dài trên mobile */
}
.nav__links {
display: flex;
flex-wrap: wrap;
gap: 0.75rem;
align-items: center;
}
Tại sao không Grid? Vì bạn không căn cột giữa các hàng nav — chỉ phân bố trên một hàng. Ép Grid vào đây thường thêm grid-template-columns thừa và khó đọc hơn Flex.
Ngoại lệ: header hai tầng (hàng 1: logo + search; hàng 2: tab) mà cột logo phải thẳng hàng với sidebar — khi đó shell ngoài là Grid, hàng menu bên trong vẫn Flex.
Dashboard / shell trang: Grid
Đây là chỗ Flex thuần hay “thắng tạm rồi thua bảo trì”: bạn bọc flex column → flex row → lại column cho đến khi vô tình tái tạo grid.
.page {
min-height: 100dvh;
display: grid;
grid-template-columns: 240px 1fr;
grid-template-rows: auto 1fr auto;
grid-template-areas:
"side header"
"side main"
"side footer";
}
.side { grid-area: side; }
.header { grid-area: header; }
.main { grid-area: main; }
.footer { grid-area: footer; }
@media (max-width: 800px) {
.page {
grid-template-columns: 1fr;
grid-template-areas:
"header"
"main"
"footer";
}
.side { display: none; } /* hoặc drawer */
}
Prototype số cột / gap / areas trên công cụ CSS Grid (chạy trên trình duyệt, copy CSS) trước khi gắn vào Astro/React — đặc biệt hữu ích khi designer đổi “sidebar 240 → 280” giữa sprint.
🏁 Tạo CSS Grid ngay tại đây
Lưới widget 12 cột
.dashboard {
display: grid;
grid-template-columns: repeat(12, 1fr);
gap: 1rem;
}
.widget--wide { grid-column: span 8; }
.widget--narrow { grid-column: span 4; }
.widget {
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.widget__actions {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
justify-content: flex-end;
align-items: center;
}
Grid đặt widget; Flex làm chrome bên trong. Reviewer nhìn một file CSS cũng hiểu ranh giới trách nhiệm.
Gallery: auto-fit + minmax
.gallery {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
gap: 1rem;
}
Card cao khác nhau vẫn căn theo track hàng — việc mà Flex + flex-wrap làm kém tự nhiên hơn (mỗi hàng là flex line riêng, không chia cột chung với hàng dưới).
Case study ngắn
1) Landing freelancer: hero + pricing
- Hero CTA nhóm nút → Flex (
gap, wrap trên mobile) - Ba cột pricing → Grid
repeat(3, 1fr)desktop; một cột mobile - Footer link → Flex wrap
Team từng dùng Flex cho cả ba cột pricing: khi một card thêm bullet dài, hai card kia không cùng baseline tiêu đề — chuyển sang Grid hết lệch trong một PR.
2) Admin CRM outsourcing
- Shell: Grid areas (sidebar cố định)
- Bảng filter chips phía trên: Flex wrap
- Grid KPI 4 ô:
repeat(4, 1fr)→repeat(2, 1fr)tablet → 1 cột phone - Mỗi KPI card: Flex column (số lớn + label + sparkline)
Myth & debug DevTools
| Nghe đồn | Thực tế |
|---|---|
| Grid thay Flex | Không — lồng tự do là bình thường |
| Cả trang luôn dùng Flex | Shell 2D sẽ sinh wrapper lồng; chuyển Grid từng vùng |
| gap trên Flex không tin được | Hỗ trợ rộng; ưu tiên gap hơn margin hack |
| stretch là bug chiều cao | Flex mặc định align-items: stretch — kiểm tra overlay trước khi đổ lỗi content |
Trong Chrome/Firefox DevTools: Grid overlay hiện track; Flex overlay hiện main/cross axis. Khi migrate Flex shell → Grid, đổi từng vùng để bisect regression (sidebar trước, rồi main).
Container queries: khi component tự phản ứng theo bề rộng parent, lựa chọn Grid/Flex ngày càng local — giảm media query toàn cục. Vẫn áp dụng cùng litmus: component đó căn 1 trục hay 2 chiều?
Subgrid: hữu ích khi lưới con phải khớp cột cha; feature-detect và progressive enhance. Đa số dashboard ổn với Grid lồng + Flex mà chưa cần subgrid.
Checklist trước khi merge layout
- Vẽ (hoặc comment) trục căn chỉnh trong PR
- Nav / actions / chips → Flex đã thử chưa?
- Shell / gallery / form cột → Grid đã thử chưa?
- Có lồng đúng hướng (Grid ngoài, Flex trong) không?
- Mobile:
flex-wraphoặc đổigrid-template-areas— đã mở DevTools chưa? - Prototype Grid phức tạp trên /vi/tools/css-grid/ rồi mới hard-code số magic
Kết
Câu hỏi đúng không phải “Flex hay Grid tốt hơn?” mà “màn hình này đang thiết kế mấy chiều?”. Nav và ruột component → Flex. Dashboard, gallery, form căn cột → Grid. Lồng hai cái là dấu hiệu team đã hiểu layout — không phải “code bẩn”. Khi vùng trang rối, mở CSS Grid tool, chỉnh hàng/cột/gap, copy CSS, rồi mới tối ưu tay.