jvinhit//lab

Search posts

Type to search across journal entries.

navigate open esc close

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 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-columnsgrid-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 */
}
ConceptControlled byDefault behavior
Explicit columnsgrid-template-columnsRequired to activate grid
Explicit rowsgrid-template-rowsnone — rows grow implicitly
Implicit column sizegrid-auto-columnsauto
Implicit row sizegrid-auto-rowsauto
Overflow placementgrid-auto-flowrow

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()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:

PatternEffect
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)); }
KeywordEmpty tracksItem behaviorUse when
auto-fillKept — still occupy spaceItems stay at minmax minimum widthYou expect more items to load (pagination, infinite scroll)
auto-fitCollapsed to 0 widthItems stretch to fill the rowFixed 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 đườnggá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-areas phả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.

PropertyValueEffect
grid-template-rowssubgridInherit parent row tracks
grid-template-columnssubgridInherit parent column tracks
grid-row / grid-columnspan NChild must span the parent’s subgrid tracks

Hỗ trợ trình duyệt (2025–2026)

Subgrid Baseline widely available từ 2025:

EngineSince
Firefox71 (2019)
Safari16 (2022)
Chrome / Edge117 (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:

  1. Firefox ship grid-template-rows: masonry sau flag
  2. Safari TP thử masonry trên grid
  3. CSSWG quyết tái dùng grid templating; display: grid-lanes nổi lên làm display value riêng
  4. 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;
}
PropertyPurposeStatus (2026)
grid-template-rows: masonryMasonry on row axis within gridExperimental; Safari TP / flags
display: grid-lanesDedicated masonry display modeSpec draft; early Safari builds
item-flow / item-packControl packing direction and densitySpec draft
JS libraries (Masonry.js, etc.)Production fallbackStable, 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; }
ValueBehaviorTrade-off
row (default)Fill left-to-right, top-to-bottomPredictable DOM order in visual layout
columnFill top-to-bottom, left-to-rightUseful for vertical magazine layouts
denseBackfill gaps with later itemsVisual density ↑, visual order ≠ DOM order

Cảnh báo accessibility: dense sắ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:

PropertyShorthandMeaning
grid-column-start / grid-column-endgrid-columnColumn line range
grid-row-start / grid-row-endgrid-rowRow line range
grid-areaNamed 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-contentalign-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 */
}
PropertyAxisScope
justify-items / align-itemsinline / blockItems within their grid area
justify-self / align-selfinline / blockSingle item override
justify-content / align-contentinline / blockTracks within the grid container
place-itemsbothShorthand for align-items + justify-items
place-contentbothShorthand 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 + minmax cho widget responsive
  • dense để xếp widget kích thước hỗn hợp
  • subgrid (progressive) cho phần bên trong widget căn hàng

Bảng quyết định nhanh

NeedReach for
Fluid card columns, unknown countrepeat(auto-fill, minmax(…))
Fluid columns, fixed item countrepeat(auto-fit, minmax(…))
Page layout with header/sidebar/maingrid-template-areas
Align rows across sibling cardsgrid-template-rows: subgrid
Pinterest-style packing (2026)grid-lanes / masonry with @supports fallback
Fill gaps with mixed-size tilesgrid-auto-flow: dense (a11y caution)
Center a fixed-size grid in viewportplace-content: center
One item breaks the rhythmgrid-column / grid-row span

Điểm chính

  1. auto-fit co track trống; auto-fill giữ chúng — chọn theo item nên giãn hay bạn chờ thêm item.
  2. grid-template-areas là cách đọc được nhất để định nghĩa page shell — đổi template cho responsive, không đổi HTML.
  3. 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.
  4. Masonry đang tới nhưng vẫn thử nghiệm năm 2026 — viết fallback @supports progressive, không bao giờ chỉ masonry.
  5. grid-auto-flow: dense cả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.