Advanced CSS Grid — Subgrid, Masonry, auto-fit vs auto-fill, and Layout Patterns
Senior CSS Grid deep-dive: auto-fit vs auto-fill, subgrid, masonry proposals, dense packing, named areas, alignment — with an interactive demo.
Tại sao Grid vẫn đáng học sâu
Flexbox giải quyết phân phối một chiều. CSS Grid là engine layout hai chiều — hàng và cột cùng lúc, với kích thước track rõ ràng, vùng đặt tên, và đặt item mà Flexbox không diễn đạt gọn được.
Hầu hết team dùng Grid cho gallery card và page shell, nhưng dừng ở repeat(auto-fill, minmax(…)). Các tính năng trong Grid Level 2 và đề xuất Grid Level 3 đang hình thành — subgrid, masonry, dense packing, named lines — giải quyết pain production thật mà hack JavaScript từng che đi.
Bài này là deep dive chỉ về Grid. Không lặp lại tối ưu CSS chung hay performance tuning.
Demo trực tiếp
Demo tương tác dưới đây gồm bốn pattern nâng cao: auto-fit vs auto-fill kèm slider chiều rộng, grid-template-areas với toggle desktop/mobile, subgrid căn hàng trên product card, và đặt item rõ ràng với điều khiển span. Mỗi panel hiện CSS readout trực tiếp.
Mở demo đầy đủ:
Grid rõ ràng vs ngầm định
bạn định nghĩa grid rõ ràng — các track khai báo trong grid-template-columns, grid-template-rows, và grid-template-areas.
Phần tràn ra ngoài track đó rơi vào grid ngầm định, kích thước bởi grid-auto-columns và grid-auto-rows và đặt bởi grid-auto-flow.
.grid {
display: grid;
/* explicit: 3 columns, 2 rows */
grid-template-columns: 200px 1fr 200px;
grid-template-rows: auto 1fr;
/* implicit: anything beyond 2 rows gets 80px height */
grid-auto-rows: 80px;
grid-auto-flow: row; /* default — fill row by row */
}
| Concept | Controlled by | Default behavior |
|---|---|---|
| Explicit columns | grid-template-columns | Required to activate grid |
| Explicit rows | grid-template-rows | none — rows grow implicitly |
| Implicit column size | grid-auto-columns | auto |
| Implicit row size | grid-auto-rows | auto |
| Overflow placement | grid-auto-flow | row |
Mô hình tư duy:
grid-template-*là bản thiết kế;grid-auto-*là dây chuyền dự phòng cho item tràn.
repeat() và minmax() — Track responsive không cần media query
mở rộng một pattern. tạo track co giãn tôn trọng kích thước tối thiểu và giãn để lấp chỗ.
.card-grid {
display: grid;
grid-template-columns: repeat(4, 1fr); /* fixed 4 equal columns */
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); /* fluid */
}
Pattern minmax() phổ biến:
| Pattern | Effect |
|---|---|
minmax(200px, 1fr) | At least 200px, grows to fill |
minmax(0, 1fr) | Can shrink below content size (fixes overflow) |
minmax(min(100%, 300px), 1fr) | Cap max contribution with min() |
repeat(3, minmax(0, 1fr)) | Three equal columns that won’t overflow |
Mẹo minmax(0, 1fr) quan trọng: track 1fr thuần có minimum ngầm là auto (kích thước content), ngăn co lại và gây tràn ngang trong layout lồng nhau. Đặt minimum là 0 cho phép track thực sự co.
auto-fit vs auto-fill — Khác biệt quan trọng
Cả hai keyword hoạt động trong repeat() khi danh sách track tạo nhiều cột hơn vừa container. Hành vi khác nhau khi còn track trống sau khi đặt hết item.
/* Both create as many 250px-minimum columns as fit */
.auto-fill { grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); }
.auto-fit { grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); }
| Keyword | Empty tracks | Item behavior | Use when |
|---|---|---|---|
auto-fill | Kept — still occupy space | Items stay at minmax minimum width | You expect more items to load (pagination, infinite scroll) |
auto-fit | Collapsed to 0 width | Items stretch to fill the row | Fixed item count — you want them to grow |
Ví dụ cụ thể: container 900px với ba card minimum 250px:
- 3 track tạo ra, 3 item ~250px mỗi cái, một track trống 250px còn lại bên phải
- 3 track tạo ra, track trống co lại, 3 item giãn ~300px mỗi cái
Quy tắc ngón tay cái: gallery số item không xác định →
auto-fill. grid feature cố định 3 card →auto-fit.
Kéo slider chiều rộng trong demo để xem track xuất hiện và biến mất — khác biệt trực quan là tức thì.
Đường đặt tên và grid-template-areas
Grid cho phép đặt tên đường và gán vùng tên, làm ý định layout đọc được trong CSS thay vì số ma thuật.
Đường đặt tên
.page {
display: grid;
grid-template-columns:
[sidebar-start] 240px
[sidebar-end main-start] 1fr
[main-end];
}
.sidebar { grid-column: sidebar-start / sidebar-end; }
.main { grid-column: main-start / main-end; }
Tên đường sống sót qua mở rộng repeat(): repeat(3, [col-start] 1fr [col-end]) tạo biến thể đánh số như col-start, col-start 2, v.v..
grid-template-areas
Vùng là shorthand cấp cao hơn — mỗi hàng chuỗi map tên ô, và . nghĩa là ô trống:
.page {
display: grid;
grid-template-columns: 240px 1fr;
grid-template-rows: auto 1fr auto;
grid-template-areas:
"header header"
"sidebar main"
"footer footer";
gap: 1rem;
min-height: 100dvh;
}
.header { grid-area: header; }
.sidebar { grid-area: sidebar; }
.main { grid-area: main; overflow: auto; }
.footer { grid-area: footer; }
Reflow responsive là đổi template — cùng HTML, map vùng khác:
@media (max-width: 768px) {
.page {
grid-template-columns: 1fr;
grid-template-areas:
"header"
"main"
"sidebar"
"footer";
}
}
Ràng buộc: mỗi hàng trong
grid-template-areasphải có cùng số ô, và vùng đặt tên phải hình chữ nhật (không hình chữ L).
Subgrid — Căn grid lồng nhau với parent
Vấn đề
Bạn có hàng card, mỗi card có title / description / price. Mỗi card là grid riêng. Không có subgrid, chiều cao hàng độc lập — mô tả ngắn ở card A không căn hàng price với card B.
Trước subgrid, team dùng đo JavaScript, hack Flexbox equal-height, hoặc chiều cao hàng cố định.
Giải pháp
Subgrid cho grid lồng nhau kế thừa đường track của parent trên trục được chỉ định:
.cards {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-template-rows: repeat(4, auto); /* shared row tracks */
gap: 1.5rem;
}
.card {
display: grid;
grid-row: span 4; /* occupy all 4 parent rows */
grid-template-rows: subgrid; /* inherit parent's row tracks */
gap: 0.5rem;
}
Giờ .title, .body, và .price nằm trên cùng row track trên mọi card, bất kể độ dài content.
Subgrid cũng hoạt động trên cột: grid-template-columns: subgrid cho component lồng ngang trong hệ thống cột chung.
| Property | Value | Effect |
|---|---|---|
grid-template-rows | subgrid | Inherit parent row tracks |
grid-template-columns | subgrid | Inherit parent column tracks |
grid-row / grid-column | span N | Child must span the parent’s subgrid tracks |
Hỗ trợ trình duyệt (2025–2026)
Subgrid Baseline widely available từ 2025:
| Engine | Since |
|---|---|
| Firefox | 71 (2019) |
| Safari | 16 (2022) |
| Chrome / Edge | 117 (2023) |
Có thể dùng subgrid production với fallback Flexbox cho trình duyệt cũ:
.card { display: flex; flex-direction: column; }
@supports (grid-template-rows: subgrid) {
.cards { grid-template-rows: repeat(4, auto); }
.card {
display: grid;
grid-row: span 4;
grid-template-rows: subgrid;
}
}
Tắt subgrid trong demo để thấy lệch hàng quay lại ngay.
CSS Masonry — Con đường dài tới packing native
Layout masonry kiểu Pinterest xếp item vào cột không khoảng trống dọc. Nhiều năm cần thư viện JS hoặc hack column-count dễ vỡ phá thứ tự đọc và điều hướng bàn phím.
Tiến hóa spec
CSSWG đã lặp cú pháp nhiều năm. Timeline xấp xỉ như sau:
- Firefox ship
grid-template-rows: masonrysau flag - Safari TP thử masonry trên grid
- CSSWG quyết tái dùng grid templating;
display: grid-lanesnổi lên làm display value riêng item-flow/item-packđịnh nghĩa hành vi packing trên trục lane
Bức tranh cú pháp (2025–2026)
Hai cú pháp cùng tồn tại trong giai đoạn chuyển tiếp:
Cũ / chuyển tiếp — masonry trên grid rows:
.gallery {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
grid-template-rows: masonry; /* experimental — check support */
gap: 1rem;
}
Mới — display grid-lanes riêng:
.gallery {
display: grid; /* fallback for older browsers */
display: grid-lanes; /* masonry when supported */
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 1rem;
}
Điều khiển packing qua item-flow:
.gallery {
display: grid-lanes;
grid-template-columns: repeat(3, 1fr);
item-flow: row dense; /* or: item-pack: dense */
gap: 1rem;
}
| Property | Purpose | Status (2026) |
|---|---|---|
grid-template-rows: masonry | Masonry on row axis within grid | Experimental; Safari TP / flags |
display: grid-lanes | Dedicated masonry display mode | Spec draft; early Safari builds |
item-flow / item-pack | Control packing direction and density | Spec draft |
| JS libraries (Masonry.js, etc.) | Production fallback | Stable, but layout-intrinsic |
Đừng ship layout chỉ masonry mà không fallback. Dùng progressive enhancement:
.card-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 1rem;
align-items: start; /* decent non-masonry fallback */
}
@supports (grid-template-rows: masonry) {
.card-grid {
grid-template-rows: masonry;
}
}
@supports (display: grid-lanes) {
.card-grid {
display: grid-lanes;
}
}
Grid thường với align-items: start cho layout bậc thang chấp nhận được ở trình duyệt không hỗ trợ. Masonry là cải thiện trực quan, không phải yêu cầu layout.
grid-auto-flow: dense — Lấp lỗ
Khi item có span hỗn hợp, auto-placement có thể để lỗ trong grid. grid-auto-flow: dense bảo trình duyệt lấp item nhỏ hơn vào lỗ trước đó.
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
grid-auto-flow: dense; /* default is 'row' — no backfill */
gap: 1rem;
}
.wide { grid-column: span 2; }
.tall { grid-row: span 2; }
.hero { grid-column: span 2; grid-row: span 2; }
| Value | Behavior | Trade-off |
|---|---|---|
row (default) | Fill left-to-right, top-to-bottom | Predictable DOM order in visual layout |
column | Fill top-to-bottom, left-to-right | Useful for vertical magazine layouts |
dense | Backfill gaps with later items | Visual density ↑, visual order ≠ DOM order |
Cảnh báo accessibility:
densesắp xếp lại item trực quan mà không đổi thứ tự DOM. Thứ tự tab focus theo DOM, không theo vị trí trực quan. Chỉ dùng cho grid trang trí mà thứ tự không quan trọng (gallery ảnh), hoặc quản lýorder/ tabindex rõ ràng.
Đặt item rõ ràng
Auto-placement là mặc định, nhưng Grid mạnh khi bạn đặt item chính xác:
.grid {
display: grid;
grid-template-columns: repeat(6, 1fr);
grid-template-rows: repeat(4, 100px);
}
.hero {
grid-column: 1 / span 3; /* start at line 1, span 3 tracks */
grid-row: 1 / span 2;
}
/* equivalent shorthand */
.hero {
grid-column: span 3;
grid-row: span 2;
}
Thuộc tính placement:
| Property | Shorthand | Meaning |
|---|---|---|
grid-column-start / grid-column-end | grid-column | Column line range |
grid-row-start / grid-row-end | grid-row | Row line range |
grid-area | — | Named area or row-start / col-start / row-end / col-end |
Dùng số đường, đường đặt tên, hoặc vùng đặt tên — chúng kết hợp tự do. Số đường âm đếm từ cuối: grid-column: 1 / -1 : 1 / -1 span full width.
Điều khiển span trong demo cho thấy một hero item thay hình auto-flow của mọi thứ khác.
Căn chỉnh — justify-*, align-*, và place-*
Căn chỉnh Grid trên hai trục:
- Trục inline (cột trong chế độ viết ngang) →
justify-* - Trục block (hàng) →
align-*
Item trong ô của chúng
.grid {
display: grid;
justify-items: center; /* inline axis — horizontal centering */
align-items: center; /* block axis — vertical centering */
place-items: center; /* shorthand for both */
}
Toàn grid trong container
Khi grid nhỏ hơn container, justify-content và align-content phân phối khoảng trống thừa giữa hoặc quanh track:
.grid {
display: grid;
grid-template-columns: repeat(3, 200px);
min-height: 100dvh;
justify-content: center; /* center tracks horizontally */
align-content: center; /* center tracks vertically */
place-content: center; /* shorthand — the classic centering trick */
}
| Property | Axis | Scope |
|---|---|---|
justify-items / align-items | inline / block | Items within their grid area |
justify-self / align-self | inline / block | Single item override |
justify-content / align-content | inline / block | Tracks within the grid container |
place-items | both | Shorthand for align-items + justify-items |
place-content | both | Shorthand for align-content + justify-content |
place-content: center trên grid với track kích thước cố định là một trong ít pattern căn giữa CSS đáng tin cậy trước khi Flexbox phổ biến.
Kết hợp — Stack Grid production
Shell dashboard thực tế kết hợp pattern trên:
.dashboard {
display: grid;
grid-template-columns: minmax(0, 240px) minmax(0, 1fr);
grid-template-rows: auto 1fr;
grid-template-areas:
"nav header"
"nav main";
min-height: 100dvh;
}
@media (max-width: 768px) {
.dashboard {
grid-template-columns: 1fr;
grid-template-areas:
"header"
"nav"
"main";
}
}
.widget-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(100%, 320px), 1fr));
gap: 1.5rem;
grid-auto-flow: dense;
}
.widget-grid .hero-widget {
grid-column: span 2;
grid-row: span 2;
}
@supports (grid-template-rows: subgrid) {
.widget-grid {
grid-template-rows: repeat(3, auto);
}
.widget {
display: grid;
grid-row: span 3;
grid-template-rows: subgrid;
gap: 0.75rem;
}
}
Stack này dùng:
- Vùng đặt tên cho shell
auto-fit+minmaxcho widget responsivedenseđể xếp widget kích thước hỗn hợpsubgrid(progressive) cho phần bên trong widget căn hàng
Bảng quyết định nhanh
| Need | Reach for |
|---|---|
| Fluid card columns, unknown count | repeat(auto-fill, minmax(…)) |
| Fluid columns, fixed item count | repeat(auto-fit, minmax(…)) |
| Page layout with header/sidebar/main | grid-template-areas |
| Align rows across sibling cards | grid-template-rows: subgrid |
| Pinterest-style packing (2026) | grid-lanes / masonry with @supports fallback |
| Fill gaps with mixed-size tiles | grid-auto-flow: dense (a11y caution) |
| Center a fixed-size grid in viewport | place-content: center |
| One item breaks the rhythm | grid-column / grid-row span |
Điểm chính
auto-fitco track trống;auto-fillgiữ chúng — chọn theo item nên giãn hay bạn chờ thêm item.grid-template-areaslà cách đọc được nhất để định nghĩa page shell — đổi template cho responsive, không đổi HTML.- Subgrid giải quyết căn hàng cross-card không cần JavaScript — sẵn sàng production trên mọi trình duyệt lớn từ 2023.
- Masonry đang tới nhưng vẫn thử nghiệm năm 2026 — viết fallback
@supportsprogressive, không bao giờ chỉ masonry. grid-auto-flow: densecải thiện mật độ trực quan đánh đổi lệch thứ tự trực quan/DOM — dùng có chủ đích.
Grid không còn là “thứ mới” — nó là công cụ mặc định cho layout hai chiều. Các tính năng nâng cao trong bài là thứ tách grid chạy được khỏi grid scale cùng design system.