Mở đầu — vì sao bài này quan trọng
Hãy thử nhớ lại lần cuối bạn phải căn một hàng nút bấm nằm ngang, hoặc đặt logo bên trái và menu bên phải trên một thanh header. Nếu bạn từng học web "đời cũ", có lẽ bạn đã vật lộn với float, clear, display: inline-block, rồi phải thêm những đoạn hack kỳ quặc để mọi thứ thẳng hàng. Cảm giác đó giống như đóng đinh bằng cán búa — làm được, nhưng đau tay và xấu xí.
Flexbox ra đời chính để xoá bỏ nỗi đau đó. Đây là module layout của CSS được thiết kế riêng cho việc sắp xếp các phần tử theo một chiều — tức là theo một hàng ngang HOẶC một cột dọc. Khoảng 95% các tình huống layout thường ngày của một web designer — thanh điều hướng, nhóm nút, thẻ sản phẩm, danh sách thông tin có icon — đều có thể giải quyết gọn gàng bằng Flexbox với vài dòng CSS.
Trong toàn bộ khoá học này, Flexbox là một trong những công cụ bạn sẽ dùng lại nhiều nhất. Bài 22 sắp tới sẽ dạy CSS Grid cho layout 2 chiều (cả hàng lẫn cột cùng lúc), còn bài này tập trung trọn vẹn vào tư duy "một chiều" của Flexbox. Khi nắm chắc nó, bạn sẽ thấy việc dàn trang nhẹ nhàng đến bất ngờ — và đây cũng là nền tảng để sau này bạn hiểu Tailwind CSS (bài 40) vốn chỉ là cách viết tắt cho chính những thuộc tính Flexbox này.
Khái niệm cốt lõi
Khi nào dùng Flexbox?
Quy tắc vàng để bạn ghi nhớ: Flexbox dành cho layout một chiều. Khi bạn muốn sắp xếp một nhóm phần tử thành một hàng ngang, hoặc một cột dọc, Flexbox là lựa chọn đúng. Khi bạn cần kiểm soát đồng thời cả hàng và cột như một bảng tính (ví dụ: bố cục dashboard phức tạp), đó là lúc dùng Grid.
Một số tình huống điển hình của Flexbox:
- Một hàng nút bấm: "Huỷ" và "Xác nhận" nằm cạnh nhau.
- Header: logo bên trái, menu ở giữa, nút "Đăng nhập" đẩy sang phải.
- Một thẻ thông tin: icon nhỏ bên trái, chữ căn giữa theo chiều dọc bên cạnh icon.
- Sidebar cố định bên trái, vùng nội dung chiếm phần còn lại bên phải.
Container và Items — mối quan hệ cha-con
Flexbox luôn hoạt động trên một cặp quan hệ: flex container (phần tử cha) và flex items (các phần tử con trực tiếp bên trong). Khi bạn khai báo display: flex trên phần tử cha, ngay lập tức tất cả các con trực tiếp của nó trở thành flex items và bắt đầu xếp hàng theo chiều ngang (mặc định).
.container {
display: flex;
}
Chỉ một dòng đó thôi đã biến các phần tử con — vốn mặc định là block và xếp chồng lên nhau theo chiều dọc — thành một hàng ngang. Đây là điểm khởi đầu của mọi thứ.
Hai trục: Main Axis và Cross Axis
Đây là khái niệm quan trọng nhất, và cũng là nơi nhiều người mới bị rối. Flexbox vận hành theo hai trục:
- Main axis (trục chính): hướng mà các items được xếp dọc theo.
- Cross axis (trục phụ): hướng vuông góc với trục chính.
flex-direction quyết định. Mặc định flex-direction: row, trục chính nằm ngang (trái sang phải), trục phụ nằm dọc. Nếu bạn đổi thành flex-direction: column, trục chính lật thành dọc, trục phụ thành ngang.Vì sao điều này quan trọng? Vì hai thuộc tính căn chỉnh justify-content và align-items luôn bám theo trục, chứ không bám theo "ngang/dọc" cố định. Cứ nhớ: justify-content căn theo trục chính, align-items căn theo trục phụ.
Các thuộc tính của Container (phần tử cha)
Đây là nhóm thuộc tính bạn đặt trên .container:
.container {
display: flex;
flex-direction: row; / row | column | row-reverse | column-reverse /
justify-content: flex-start; / căn theo trục chính /
align-items: stretch; / căn theo trục phụ /
flex-wrap: nowrap; / nowrap | wrap /
gap: 16px; / khoảng cách giữa các items /
}
justify-content: phân bố items theo trục chính. Giá trị hay dùng:flex-start(dồn về đầu),flex-end(dồn về cuối),center(giữa),space-between(cách đều, hai item ngoài cùng dính mép),space-around,space-evenly.align-items: căn items theo trục phụ. Giá trị hay dùng:stretch(kéo giãn lấp đầy — mặc định),center(căn giữa theo chiều phụ),flex-start,flex-end,baseline(căn theo đường chân chữ).flex-wrap: cho phép items xuống hàng khi không đủ chỗ. Mặc địnhnowrapnghĩa là items sẽ co lại để cố nhét vào một hàng. Đặtwrapđể chúng tự xuống hàng — cực kỳ hữu ích cho lưới thẻ responsive.gap: tạo khoảng cách đều giữa các items. Đây là cách hiện đại, sạch hơn nhiều so với việc thêmmargincho từng item.
Các thuộc tính của Items (phần tử con)
Đây là nhóm thuộc tính bạn đặt trên từng flex item:
.item {
flex-grow: 0; / tỉ lệ "ăn" phần không gian thừa /
flex-shrink: 1; / tỉ lệ co lại khi thiếu chỗ /
flex-basis: auto;/ kích thước cơ sở trước khi grow/shrink /
flex: 1; / viết tắt cho 3 cái trên: grow shrink basis /
align-self: auto;/ ghi đè align-items cho riêng item này /
}
Thuộc tính flex là viết tắt quan trọng nhất. flex: 1 nghĩa là item này sẽ giãn ra chiếm hết không gian còn lại. Nếu hai item cùng flex: 1, chúng chia đôi không gian; nếu một item flex: 2 và một item flex: 1, chúng chia theo tỉ lệ 2:1. Đây chính là chìa khoá để làm bố cục "sidebar cố định + nội dung co giãn".
Tình huống thực tế
Ví dụ 1 — Thanh header của Tiki
Hãy hình dung bạn đang dựng lại thanh header cho một sàn thương mại điện tử kiểu Tiki. Trên header có: logo bên trái, một ô tìm kiếm chiếm phần lớn ở giữa, và cụm icon giỏ hàng + tài khoản bên phải. Đây là bài toán một chiều kinh điển.
.header {
display: flex;
align-items: center; / mọi thứ căn giữa theo chiều dọc /
gap: 24px;
padding: 12px 24px;
}
.search-box {
flex: 1; / ô tìm kiếm "ăn" hết khoảng trống ở giữa /
}
Cái hay nằm ở chỗ: logo và cụm icon giữ nguyên kích thước tự nhiên của chúng, còn flex: 1 trên ô tìm kiếm khiến nó tự động giãn ra lấp đầy mọi khoảng trống còn lại. Khi người dùng phóng to/thu nhỏ cửa sổ trình duyệt, ô tìm kiếm tự co giãn theo, logo và icon đứng yên. Trước thời Flexbox, để đạt hiệu ứng này người ta phải tính toán phần trăm chiều rộng thủ công và rất dễ vỡ layout. Bài học rút ra: dùng flex: 1 cho phần tử bạn muốn "linh hoạt", để nguyên các phần tử bạn muốn "cố định".
Ví dụ 2 — Lưới sản phẩm tự xuống hàng của một shop trên Shopee
Một bạn freelancer ở TP.HCM nhận làm trang danh mục cho một shop mỹ phẩm, hiển thị 12 thẻ sản phẩm. Trên màn hình laptop muốn 4 thẻ một hàng, nhưng khi xem trên điện thoại thì các thẻ phải tự xuống hàng cho gọn. Đây là nơi flex-wrap toả sáng.
.product-grid {
display: flex;
flex-wrap: wrap;
gap: 16px;
}
.product-card {
flex: 1 1 240px; / grow 1, shrink 1, rộng cơ sở 240px /
}
Dòng flex: 1 1 240px có nghĩa: mỗi thẻ muốn rộng tối thiểu khoảng 240px, nhưng được phép giãn ra để lấp đầy hàng và được phép co lại khi cần. Trình duyệt sẽ tự tính: trên màn hình rộng nhét được 4 thẻ thì xếp 4, màn hình hẹp chỉ vừa 2 thẻ thì tự xuống hàng còn 2. Bạn freelancer này không cần viết một dòng media query nào cho riêng phần lưới — chỉ Flexbox lo hết. Bài học: kết hợp flex-wrap: wrap với flex-basis là một mẹo cực mạnh để có lưới linh hoạt mà gần như không cần breakpoint thủ công.
Ví dụ 3 — Đẩy footer xuống đáy trang trong một app nội bộ
Một công ty fintech ở Singapore xây trang quản trị nội bộ. Vấn đề quen thuộc: khi nội dung ít, footer "trôi" lên giữa màn hình thay vì nằm dưới đáy, trông rất nghiệp dư. Giải pháp Flexbox theo chiều dọc:
body {
display: flex;
flex-direction: column;
min-height: 100vh; / tối thiểu cao bằng màn hình /
}
.main-content {
flex: 1; / phần nội dung giãn ra đẩy footer xuống /
}
Ở đây trục chính được lật thành dọc (column). flex: 1 trên vùng nội dung khiến nó nuốt hết khoảng trống dọc còn thừa, tự nhiên đẩy footer chạm đáy. Dù trang có ít hay nhiều nội dung, footer luôn nằm đúng chỗ. Bài học: Flexbox không chỉ cho hàng ngang — flex-direction: column giải quyết gọn những bài toán dọc mà trước đây phải dùng position: absolute đầy rủi ro.
Hướng dẫn từng bước
Hãy thực hành dựng một thanh navigation hoàn chỉnh để thấy quy trình tư duy.
Bước 1 — Xác định đây là bài toán một chiều. Logo, các link menu, nút CTA — tất cả nằm trên một hàng ngang. Đúng "đất diễn" của Flexbox.
Bước 2 — Dựng HTML với quan hệ cha-con rõ ràng.
<nav class="navbar">
<div class="logo">VietnamCos</div>
<ul class="menu">
<li>Trang chủ</li>
<li>Khoá học</li>
<li>Mentor</li>
</ul>
<button class="cta">Đăng ký</button>
</nav>
Bước 3 — Bật flex trên container.
.navbar {
display: flex;
align-items: center; / căn giữa theo chiều dọc cho mọi item /
gap: 32px;
padding: 16px 24px;
}
Bước 4 — Phân bố không gian. Bạn muốn logo bên trái, nút đăng ký bị đẩy hẳn sang phải. Mẹo kinh điển: cho phần menu flex: 1 để nó "chiếm chỗ" và đẩy nút CTA ra rìa phải.
.menu {
flex: 1;
display: flex; / menu cũng là một flex container con /
gap: 24px;
}
Lưu ý điểm thú vị: .menu vừa là item của .navbar, vừa là container cho các <li> bên trong. Flexbox lồng nhau (nested flex) là chuyện hết sức bình thường và bạn sẽ dùng rất nhiều.
Bước 5 — Kiểm tra trên màn hình hẹp. Thu nhỏ trình duyệt. Nếu các item bị chật, cân nhắc thêm flex-wrap: wrap hoặc xử lý riêng cho mobile (bạn sẽ học sâu hơn ở các bài mobile-first và breakpoints). Với Flexbox, phần lớn việc đã tự co giãn ổn ngay từ đầu.
Lỗi thường gặp & mẹo
Nhầm justify-content với align-items. Đây là lỗi số một. Hãy nhớ câu thần chú: justify-content đi theo trục chính (mặc định là ngang), align-items đi theo trục phụ (mặc định là dọc). Khi bạn lật flex-direction: column, hai trục đổi vai, nên đừng ngạc nhiên nếu align-items: center bỗng căn theo chiều ngang.
Quên rằng chỉ con trực tiếp mới là flex item. Nếu bạn bọc thêm một lớp <div> trung gian, Flexbox chỉ tác động lên lớp đó, không "xuyên" xuống cháu chắt. Hãy kiểm tra lại cấu trúc DOM khi căn chỉnh không như ý.
Dùng margin thay vì gap. Code cũ hay thêm margin-right cho mỗi item rồi phải xử lý item cuối thừa margin. Hiện nay tất cả trình duyệt hiện đại đều hỗ trợ gap trên Flexbox — hãy dùng nó, sạch và dễ bảo trì hơn nhiều.
Item bị bóp méo khi không đủ chỗ. Mặc định flex-shrink: 1 cho phép item co lại, đôi khi co quá mức làm ảnh hoặc nút bị biến dạng. Nếu muốn một item giữ nguyên kích thước, đặt flex-shrink: 0 cho nó.
Mẹo margin-left: auto. Một thủ thuật rất gọn: muốn đẩy riêng một item về cuối hàng mà không động đến các item khác, đặt margin-left: auto cho item đó. Flexbox sẽ nuốt hết khoảng trống bên trái nó, đẩy nó ra rìa.
Mẹo dùng flex: 1 cho cột bằng chiều cao. Khi xếp các thẻ ngang hàng, align-items: stretch (mặc định) tự động làm chúng cao bằng nhau — bạn không cần set chiều cao thủ công.
Bài tập thực hành
- Hàng nút bấm: Dựng một khối có hai nút "Huỷ" và "Lưu thay đổi". Dùng
justify-content: flex-endđể dồn cả hai về bên phải,gap: 12pxđể tạo khoảng cách. Sau đó thử đổi sangspace-betweenvà quan sát sự khác biệt.
- Thẻ thông tin có icon: Tạo một thẻ với một icon (dùng ô vuông màu giả lập) bên trái và hai dòng chữ (tiêu đề + mô tả) bên phải. Dùng Flexbox để icon và khối chữ căn giữa theo chiều dọc với nhau (
align-items: center).
- Lưới thẻ tự xuống hàng: Tạo 6 thẻ sản phẩm với
flex: 1 1 200pxvàflex-wrap: wrap. Thu phóng cửa sổ trình duyệt để xem các thẻ tự sắp lại từ 3 hàng thành 2 rồi 1.
- Layout cả trang dọc: Áp dụng kỹ thuật "sticky footer" ở Ví dụ 3 — dùng
flex-direction: columnvàflex: 1để đẩy footer xuống đáy trang dù nội dung ít.
Tóm tắt
Flexbox là công cụ layout một chiều — sắp xếp phần tử theo một hàng ngang hoặc một cột dọc. Bạn bật nó bằng display: flex trên phần tử cha (container), biến các con trực tiếp thành flex items.
Hãy ghi nhớ ba điều cốt lõi: thứ nhất, mọi căn chỉnh đều bám theo trục chính (justify-content) và trục phụ (align-items), và hai trục này đổi vai khi bạn đổi flex-direction. Thứ hai, flex: 1 là chìa khoá để cho một item co giãn lấp đầy không gian thừa, trong khi các item khác giữ nguyên. Thứ ba, flex-wrap: wrap kết hợp flex-basis cho bạn lưới linh hoạt gần như không cần media query.
Flexbox xử lý đẹp khoảng 95% nhu cầu layout thường ngày của bạn. Khi nào cần điều khiển đồng thời cả hàng lẫn cột như một bảng — đó là lúc gọi đến CSS Grid, chủ đề của bài tiếp theo.