Vài năm trước, tôi đã ngồi xuống để xây dựng một dashboard. Không có gì cầu kỳ cả—chỉ là một thanh sidebar cố định, một vùng nội dung chính và một footer. Flexbox có vẻ là lựa chọn hiển nhiên, và lúc đầu, nó hoạt động khá ổn. Thế rồi tôi đưa một hàng các thẻ card vào phần nội dung chính. Toàn bộ bố cục lập tức đổ sụp. Tôi đã dành ba giờ tiếp theo để vật lộn với các tỷ lệ flex-grow và các độ rộng phần trăm lồng nhau, nhìn các container từ chối căn hàng dù tôi có giảm bớt bao nhiêu padding đi chăng nữa. Trang web lúc đó cảm giác như một tòa tháp sắp đổ.

Trải nghiệm đau đớn đó đã dạy tôi một điều quan trọng nhất về bố cục CSS hiện đại: Flexbox và Grid không phải là đối thủ của nhau. Chúng là những công cụ cho các nhiệm vụ khác nhau, và hầu hết các bố cục thất bại đều đến từ việc chọn sai công cụ.

Sự khác biệt nằm ở số chiều

Hãy coi Flexbox như một hành lang. Các mục chảy theo một hướng, hoặc là hàng hoặc là cột. Nó được xây dựng để phân bổ một chiều. Trình duyệt sẽ nhìn vào nội dung, sau đó quyết định cách kéo giãn, thu nhỏ hoặc căn chỉnh nó dọc theo trục duy nhất đó. Điều này làm cho Flexbox trở nên hoàn hảo trong những tình huống mà nội dung quyết định bố cục. Một thanh điều hướng, một chồng các trường biểu mẫu, hoặc một tập hợp các thẻ tag cần khoảng cách đều nhau đều nằm tự nhiên trong một flex container vì chúng chia sẻ cùng một logic trên một dòng.

Ngược lại, Grid giống như một sơ đồ mặt bằng. Nó hoàn toàn là hai chiều, xử lý cả hàng và cột cùng một lúc. Bạn xác định cấu trúc trước, sau đó bạn đặt các mục vào bên trong đó. Điều này làm cho Grid trở nên lý tưởng cho kiến trúc tổng thể của trang—bất cứ nơi nào bạn cần kiểm soát vị trí theo cả chiều dọc và chiều ngang cùng lúc. Khi bạn cố gắng ép một thiết kế hai chiều phức tạp thông qua các container Flexbox lồng nhau, bạn sẽ kết thúc với một cấu trúc markup dày đặc, các lớp bao bọc mong manh và các phép tính toán dễ lỗi, vốn sẽ tan rã ngay khi bạn thêm vào một phần tử bổ sung.

Flexbox vẫn giữ vững vai trò của mình

Đừng vội vàng thay thế mọi flex container bằng Grid. Bên trong các thành phần riêng lẻ, Flexbox thường sạch sẽ và trực quan hơn. Nếu bạn có một card footer với hai nút và bạn muốn một nút căn trái và một nút căn phải, justify-content: space-between sẽ thực hiện chính xác những gì bạn muốn. Nếu bạn cần căn giữa một icon theo chiều dọc bên trong một nút có chiều cao không xác định, align-items: center là con đường đơn giản nhất. Nội dung biến thiên—chẳng hạn như một hàng các huy hiệu người dùng với độ dài tên khác nhau—sẽ nằm gọn gàng trong Flexbox vì nó ưu tiên nội dung (content-first). Container sẽ thích nghi với những gì nó chứa.

Grid thì ưu tiên container (container-first). Bạn nói với trình duyệt có bao nhiêu track và chúng nên lớn bao nhiêu, sau đó bạn thả các mục vào các ô đó. Nếu bố cục của bạn thực sự chỉ là một dòng duy nhất các phần tử anh em chia sẻ không gian, Grid sẽ tạo ra sự rườm rà không cần thiết.

Bố cục trang thuộc về Grid

Đây là nơi Grid tiếp quản. Bạn còn nhớ cơn ác mộng dashboard của tôi không? Đây là cách cấu trúc đó nên trông như thế nào ngay từ đầu:

.page {
  display: grid;
  min-height: 100vh;
  grid-template-rows: auto 1fr auto;
  grid-template-columns: 200px 1fr;
}

.header { grid-row: 1; grid-column: 1 / -1; }
.sidebar { grid-row: 2; grid-column: 1; }
.main { grid-row: 2; grid-column: 2; }
.footer { grid-row: 3; grid-column: 1 / -1; }

Hãy nhìn vào những gì đang diễn ra. Container .page thiết lập ba hàng và hai cột. Hàng đầu tiên tự điều chỉnh kích thước theo bất kỳ nội dung nào có trong header nhờ vào auto. Hàng giữa chiếm toàn bộ không gian còn lại bằng 1fr. Footer nằm ở hàng cuối cùng, kích thước theo nội dung của chính nó. Trong khi đó, sidebar được cố định ở độ rộng 200 pixel, và vùng chính sẽ lấp đầy phần còn lại.

Logic sắp xếp mới là sự giải thoát thực sự. Header trải dài từ đường cột 1 đến đường -1, nghĩa là nó sẽ kéo dài qua cả hai cột bất kể sau này bạn có thêm bao nhiêu cột đi chăng nữa. Sidebar và nội dung chính chia sẻ hàng 2 nhưng chiếm các cột riêng biệt. Footer quay trở lại độ rộng đầy đủ ở hàng 3. Bạn không cần các phép tính phần trăm trên các phần tử con. Bạn không cần các lớp bao bọc lồng nhau tranh giành chiều cao. Chỉ cần thay đổi độ rộng của sidebar ở một nơi, và vùng chính sẽ tự động điều chỉnh. Toàn bộ bố cục vận hành nhịp nhàng như một hệ thống thống nhất.

Khả năng đọc hiểu với grid-template-areas

Để rõ ràng hơn nữa, bạn có thể đặt tên cho các phần của mình. Thay vì quản lý các số thứ tự đường kẻ, bạn viết một bản đồ trực quan:

.page {
  grid-template-areas:
    "header header"
    "sidebar main"
    "footer footer";
}

.header { grid-area: header; }
.main { grid-area: main; }

Giờ đây, stylesheet của bạn đọc giống như một bản phác thảo (wireframe). Nếu bạn muốn sidebar xuất hiện ở bên phải cho một breakpoint khác, bạn chỉ cần hoán đổi hai từ trong chuỗi template. Loại khả năng đọc hiểu về mặt cấu trúc đó là điều không thể đạt được bên trong một "tòa tháp" các container flex lồng nhau.

Hai bài học xương máu

Grid không phải là phép màu. Có hai sai lầm sẽ phá hỏng bố cục của bạn một cách nhanh chóng.

Đầu tiên là việc sử dụng đơn vị thiếu chính xác. Nếu bạn định nghĩa grid-template-columns: 1fr 1fr 1fr nhưng thiết kế của bạn chỉ cần hai cột nội dung, thì track thứ ba đó vẫn tồn tại. Nó sẽ để trống và tạo ra một khoảng hở ngoài ý muốn, hoặc nó sẽ ép nội dung thực tế của bạn phải co lại theo những cách làm phá vỡ thiết kế. Hãy làm việc một cách có chủ đích. Khi bạn muốn có hai cột, hãy viết repeat(2, 1fr).