jvinhit//lab

Search posts

Type to search across journal entries.

navigate open esc close

CSS Grid — The Complete Guide to Every Property (with an Interactive Demo)

A complete, practical CSS Grid reference: tracks, the fr unit, minmax & auto-fit, template-areas, alignment, item placement, and every container/item property — with a live interactive demo.

14 MIN READ

Why One More Grid Article {Vì sao lại thêm một bài về Grid}

Most Grid tutorials teach you four properties and stop {Phần lớn tutorial Grid dạy bạn bốn thuộc tính rồi dừng}. Then you hit a real layout — a dashboard, a holy-grail shell, a responsive gallery — and reach for fragile hacks {Rồi bạn gặp layout thật — dashboard, holy-grail shell, gallery responsive — và phải dùng hack mong manh}. This post is the opposite {Bài này thì ngược lại}: a complete map of every Grid property, grouped by where it lives (container vs item), each with the smallest example that makes it click {một bản đồ đầy đủ mọi thuộc tính Grid, nhóm theo nơi nó sống (container vs item), mỗi cái kèm ví dụ nhỏ nhất để hiểu ngay}.

Pair it with the live demo below — change a control, read the generated CSS {Hãy dùng kèm demo bên dưới — chỉnh control, đọc CSS sinh ra}.


The Live Demo {Demo trực tiếp}

Seven tabs, one per concept: track builder, the fr unit, auto-fit + minmax, grid-template-areas, alignment, item placement, and a full property reference {Bảy tab, mỗi tab một khái niệm: track builder, đơn vị fr, auto-fit + minmax, grid-template-areas, alignment, đặt vị trí item, và bảng tra thuộc tính đầy đủ}.

Open the full demo {Mở demo đầy đủ}: /tools/css-grid-complete-demo/.


The Mental Model {Mô hình tư duy}

Grid is two-dimensional {Grid là hai chiều}: you control rows and columns at the same time {bạn kiểm soát hàng cột cùng lúc}. Three nouns are enough to reason about any grid {Ba danh từ là đủ để suy luận mọi grid}:

  • Tracks — the columns and rows themselves {các cột và hàng}.
  • Lines — the numbered dividers between tracks; n tracks means n + 1 lines {các đường phân cách có đánh số; n track thì có n + 1 line}.
  • Cells / Areas — a single track intersection, or a rectangle of cells you name {một giao điểm track, hoặc một hình chữ nhật gồm nhiều cell mà bạn đặt tên}.

Everything below is just sizing tracks, or placing items against lines/areas {Mọi thứ bên dưới chỉ là chỉnh kích thước track, hoặc đặt item theo line/area}.

.container {
  display: grid; /* or inline-grid */
}

display: grid makes direct children grid items {display: grid biến các con trực tiếp thành grid item}. Text nodes and ::before/::after become items too {Text node và ::before/::after cũng thành item}.


Part 1 — Defining Tracks {Phần 1 — Định nghĩa track}

grid-template-columns and grid-template-rows declare the explicit grid {grid-template-columnsgrid-template-rows khai báo grid explicit}.

.container {
  display: grid;
  grid-template-columns: 200px 1fr 200px; /* fixed · flexible · fixed */
  grid-template-rows: 64px 1fr;           /* header · body */
  gap: 16px;                               /* row & column gutter */
}

A few sizing options you will actually use {Vài cách định kích thước bạn sẽ thật sự dùng}:

Value {Giá trị}Meaning {Ý nghĩa}
200px, 10remFixed track {Track cố định}
autoSized to content (or leftover space) {Theo content (hoặc không gian còn lại)}
1frA fraction of leftover free space {Một phần không gian còn lại}
min-contentSmallest the content can be {Nhỏ nhất content có thể}
max-contentLargest the content wants {Lớn nhất content muốn}
minmax(a, b)Clamp between a and b {Kẹp giữa ab}
fit-content(x)max-content, capped at x {max-content nhưng giới hạn ở x}

gap, row-gap, column-gap {khoảng cách}

.container {
  gap: 16px;            /* both axes */
  gap: 12px 24px;       /* row-gap column-gap */
  column-gap: 24px;     /* individually */
}

gap only adds space between tracks, never on the outer edges {gap chỉ thêm khoảng cách giữa các track, không bao giờ ở mép ngoài}.

Try it {Thử ngay}: open the demo’s Tracks tab and change columns, rows, gap, and item count {mở tab Tracks của demo và đổi columns, rows, gap, số item}.


Part 2 — The fr Unit {Phần 2 — Đơn vị fr}

fr distributes the leftover space after fixed tracks and gaps are subtracted {fr phân chia không gian còn lại sau khi trừ track cố định và gap}. This is the single most important thing to internalize {Đây là điều quan trọng nhất cần thấm}:

/* 120px is removed first; the remaining width splits 1 : 2 */
grid-template-columns: 120px 1fr 2fr;

fr is greedy but content-aware {fr tham lam nhưng vẫn để ý content}: a 1fr track will not shrink below its content’s min-content size unless you add minmax(0, 1fr) {một track 1fr sẽ không co nhỏ hơn min-content của content trừ khi bạn dùng minmax(0, 1fr)}.

/* The classic "why won't my grid item shrink?" fix */
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);

This minmax(0, 1fr) trick is the cure for overflowing text or charts inside grid items {Mẹo minmax(0, 1fr) là cách chữa khi text hay chart tràn ra ngoài grid item}.

Try it {Thử ngay}: the fr unit tab lets you mix a fixed column with several fr tracks {tab fr unit cho bạn trộn một cột cố định với nhiều track fr}.


Part 3 — Responsive Without Media Queries {Phần 3 — Responsive không cần media query}

The single most useful Grid pattern {Pattern Grid hữu ích nhất}:

.gallery {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 1rem;
}

Read it right to left {Đọc từ phải sang trái}: each column is at least 220px, at most 1fr; the browser fits as many columns as the width allows {mỗi cột tối thiểu 220px, tối đa 1fr; trình duyệt nhồi nhiều cột nhất mà chiều rộng cho phép}. No breakpoints {Không breakpoint}.

auto-fit vs auto-fill {auto-fit và auto-fill}

The difference shows only when there are fewer items than columns {Khác biệt chỉ lộ ra khi item ít hơn số cột}:

auto-fitauto-fill
Empty tracks {Track rỗng}Collapsed to 0 {Co về 0}Kept at min size {Giữ ở kích thước min}
Visible effect {Hiệu ứng}Items stretch to fill the row {Item giãn lấp đầy hàng}Items keep their size; gaps remain {Item giữ size; còn khoảng trống}

Try it {Thử ngay}: the auto-fit / minmax tab has a container-width slider so you can watch columns wrap {tab auto-fit / minmax có slider chiều rộng container để bạn xem cột wrap}.


Part 4 — grid-template-areas {Phần 4 — Vùng đặt tên}

Name regions in ASCII, then drop items into them {Đặt tên vùng bằng ASCII, rồi thả item vào}. This is the most readable way to build page shells {Đây là cách dễ đọc nhất để dựng page shell}:

.app {
  display: grid;
  grid-template-columns: 200px 1fr 200px;
  grid-template-rows: auto 1fr auto;
  grid-template-areas:
    "header header header"
    "nav    main   aside"
    "footer footer footer";
  min-height: 100dvh;
}

.app > header { grid-area: header; }
.app > nav    { grid-area: nav; }
.app > main   { grid-area: main; }
.app > aside  { grid-area: aside; }
.app > footer { grid-area: footer; }

Rules that trip people up {Vài luật hay khiến người ta vấp}:

  • Every row string must have the same number of columns {Mỗi chuỗi hàng phải có cùng số cột}.
  • A repeated name spanning cells must form a rectangle {Tên lặp lại để span phải tạo thành hình chữ nhật}.
  • Use . for an empty cell {Dùng . cho ô trống}.
  • Re-defining areas at a media query is how you reflow to mobile {Định nghĩa lại areas trong media query là cách reflow sang mobile}.
@media (max-width: 640px) {
  .app {
    grid-template-columns: 1fr;
    grid-template-areas:
      "header"
      "main"
      "nav"
      "footer";
  }
  .app > aside { display: none; }
}

Try it {Thử ngay}: the template-areas tab switches between holy-grail, dashboard, and stacked layouts {tab template-areas chuyển giữa holy-grail, dashboard, và stacked}.


Part 5 — Alignment {Phần 5 — Canh chỉnh}

Grid has two alignment scopes, and mixing them up is the #1 source of confusion {Grid có hai phạm vi alignment, nhầm chúng là nguyên nhân bối rối số 1}:

  • *-items / *-self — align content inside each cell {canh content trong từng cell}.
  • *-content — align the whole grid inside the container when the grid is smaller than it {canh cả lưới trong container khi lưới nhỏ hơn container}.

And two axes {Và hai trục}:

  • justify-* — the inline (row / horizontal in LTR) axis {trục inline (hàng / ngang trong LTR)}.
  • align-* — the block (column / vertical) axis {trục block (cột / dọc)}.
.container {
  /* content inside every cell */
  justify-items: center;   /* inline axis */
  align-items: center;     /* block axis */
  place-items: center;     /* shorthand: align-items / justify-items */

  /* the whole grid within the container */
  justify-content: space-between;
  align-content: center;
  place-content: center / space-between;
}

/* override one item */
.featured {
  justify-self: end;
  align-self: start;
  place-self: start end;   /* align-self / justify-self */
}
Property {Thuộc tính}Scope {Phạm vi}Axis {Trục}
justify-items / justify-selfCell content / one itemInline {Inline}
align-items / align-selfCell content / one itemBlock {Block}
justify-contentWhole gridInline {Inline}
align-contentWhole gridBlock {Block}

Try it {Thử ngay}: the Alignment tab exposes all four container properties at once {tab Alignment mở cả bốn thuộc tính container cùng lúc}.


Part 6 — Placing Items {Phần 6 — Đặt item}

By default items auto-flow into the next available cell {Mặc định item tự chảy vào cell trống kế tiếp}. Override placement with line numbers or span {Ghi đè vị trí bằng số line hoặc span}:

.hero {
  grid-column: 1 / 3;   /* from line 1 to line 3 → spans 2 columns */
  grid-row: 1 / 2;
}

.full-bleed {
  grid-column: 1 / -1;  /* first line to LAST line — full width */
}

.wide {
  grid-column: span 2;  /* span 2 tracks from wherever it lands */
}

Line counting starts at 1 on the left/top; -1 is the last line {Đếm line bắt đầu từ 1 ở trái/trên; -1 là line cuối}. grid-area is the four-value shorthand {grid-area là shorthand bốn giá trị}:

/* grid-area: row-start / col-start / row-end / col-end */
.card { grid-area: 1 / 1 / 3 / 3; }

grid-auto-flow and implicit tracks {grid-auto-flow và track ngầm}

When items land outside the explicit grid, Grid creates implicit tracks {Khi item rơi ra ngoài grid explicit, Grid tạo track ngầm}, sized by grid-auto-rows / grid-auto-columns {kích thước theo grid-auto-rows / grid-auto-columns}:

.masonry-ish {
  grid-template-columns: repeat(3, 1fr);
  grid-auto-rows: 120px;       /* every implicit row is 120px */
  grid-auto-flow: row dense;   /* backfill holes left by spanning items */
}

dense backfills gaps left by larger items {dense lấp lại các lỗ hổng do item lớn để lại} — great for galleries, but it can reorder items visually away from DOM order, which hurts keyboard/tab flow {tuyệt cho gallery, nhưng có thể đảo thứ tự hình ảnh so với DOM, ảnh hưởng luồng keyboard/tab}.

Try it {Thử ngay}: the Item placement tab applies grid-column / grid-row / grid-auto-flow to one highlighted item {tab Item placement áp grid-column / grid-row / grid-auto-flow lên một item được tô sáng}.


Part 7 — Named Lines & subgrid {Phần 7 — Line đặt tên & subgrid}

You can name lines and reference them instead of numbers {Bạn có thể đặt tên line và tham chiếu thay vì số}:

.container {
  grid-template-columns: [full-start] 1fr [content-start] minmax(0, 64rem) [content-end] 1fr [full-end];
}
.content  { grid-column: content-start / content-end; }
.bleed    { grid-column: full-start / full-end; }

repeat() with names yields auto-numbered lines like col 1, col 2 {repeat() kèm tên tạo line tự đánh số như col 1, col 2}:

grid-template-columns: repeat(3, [col] 1fr);
.item { grid-column: col 2 / span 1; }

subgrid lets a nested grid inherit its parent’s track lines so children align across components {subgrid cho grid lồng nhau kế thừa line track của cha để con căn thẳng hàng xuyên component}:

.card {
  display: grid;
  grid-template-rows: subgrid;   /* adopt the parent's row tracks */
  grid-row: span 3;
}

For deeper subgrid/masonry patterns, see {Để xem subgrid/masonry sâu hơn, đọc}: Advanced Grid — subgrid & masonry.


Every Property at a Glance {Toàn bộ thuộc tính trong một bảng}

On the container {Trên container}:

Property {Thuộc tính}Job {Nhiệm vụ}
display: grid | inline-gridCreate the grid {Tạo grid}
grid-template-columns / -rowsSize explicit tracks {Kích thước track explicit}
grid-template-areasName regions {Đặt tên vùng}
grid-templateShorthand: rows / columns (+ areas) {Shorthand}
gap / row-gap / column-gapGutters {Khoảng cách}
grid-auto-columns / -rowsSize implicit tracks {Kích thước track ngầm}
grid-auto-flowAuto-placement direction + dense {Hướng tự đặt + dense}
justify-items / align-items / place-itemsAlign cell content {Canh content trong cell}
justify-content / align-content / place-contentAlign the whole grid {Canh cả lưới}
gridMaster shorthand {Shorthand tổng}

On an item {Trên item}:

Property {Thuộc tính}Job {Nhiệm vụ}
grid-column / grid-rowPlace via lines / span {Đặt theo line / span}
grid-areaArea name, or 4-line shorthand {Tên area, hoặc shorthand 4 line}
justify-self / align-self / place-selfAlign this one item {Canh riêng item này}
orderReorder within auto-flow {Đổi thứ tự trong auto-flow}

The demo’s All properties tab is the same reference, kept handy next to live examples {Tab All properties trong demo là bảng tra y hệt, để cạnh ví dụ trực tiếp}.


Production Pitfalls {Cạm bẫy thực chiến}

  1. Overflow from 1fr {Tràn vì 1fr}: a 1fr track respects content’s min-content. Use minmax(0, 1fr) to allow shrinking {Track 1fr tôn trọng min-content. Dùng minmax(0, 1fr) để cho co}.
  2. gap is not margin {gap không phải margin}: no edge spacing; pad the container instead {không có khoảng cách mép; hãy pad container}.
  3. dense breaks tab order {dense phá thứ tự tab}: visual order diverges from DOM order — avoid for interactive content {thứ tự hình ảnh lệch DOM — tránh với content tương tác}.
  4. order is visual only {order chỉ thị giác}: like dense, it does not move DOM focus order {giống dense, không đổi thứ tự focus của DOM}.
  5. 100% vs 100dvh shells {shell 100% vs 100dvh}: use min-height: 100dvh (not height) so content can grow {dùng min-height: 100dvh (không phải height) để content giãn được}.
  6. Percent inside fr {Percent trong fr}: avoid % tracks mixed with fr; they measure different things and fight {tránh trộn track % với fr; chúng đo khác nhau và xung đột}.

Exercises {Bài tập}

Use the demo to check your answers {Dùng demo để kiểm tra đáp án}.

  1. Build a responsive card gallery where cards are at least 240px and stretch to fill the row, with 1.25rem gaps — without any media query {Dựng gallery card responsive: card tối thiểu 240px, giãn lấp đầy hàng, gap 1.25remkhông media query}.
  2. Create a holy-grail layout with grid-template-areas, then make it stack to a single column under 640px and hide the aside {Tạo holy-grail bằng grid-template-areas, rồi cho stack thành một cột dưới 640px và ẩn aside}.
  3. In a 4-column grid, make the first item a full-width hero using grid-column: 1 / -1, then turn on grid-auto-flow: dense and observe the reflow {Trong grid 4 cột, biến item đầu thành hero full-width bằng grid-column: 1 / -1, rồi bật grid-auto-flow: dense và quan sát reflow}.
  4. Stretch {Nâng cao}: center a single element both axes using only Grid, in two different ways (place-items vs place-content vs margin: auto) and explain when each differs {Căn giữa một phần tử cả hai trục chỉ bằng Grid, theo nhiều cách (place-items vs place-content vs margin: auto) và giải thích khi nào chúng khác nhau}.
Solution sketch {Gợi ý lời giải}
/* 1 */
.gallery { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 1.25rem; }

/* 2 — see Part 4; swap grid-template-areas in the media query and display:none the aside */

/* 3 */
.grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); grid-auto-flow: dense; }
.grid > :first-child { grid-column: 1 / -1; }

/* 4 — any of these centers a single child */
.a { display: grid; place-items: center; }                 /* aligns the item in its cell */
.b { display: grid; place-content: center; }               /* aligns the whole (1-cell) grid */
.c { display: grid; }  .c > * { margin: auto; }            /* auto margins eat free space */

place-items centers the item inside its track; place-content centers the track block inside the container — identical for a single full-size cell, different once tracks are smaller than the container {place-items căn item trong track; place-content căn khối track trong container — giống nhau với một cell full-size, khác khi track nhỏ hơn container}.


Wrap-up {Tổng kết}

Grid is small {Grid nhỏ gọn}: a handful of container properties to size tracks, a handful of item properties to place things, and two alignment scopes {vài thuộc tính container để chỉnh track, vài thuộc tính item để đặt vị trí, và hai phạm vi alignment}. Once fr, minmax, auto-fit, and grid-template-areas are muscle memory, most layouts become a few honest lines of CSS {Khi fr, minmax, auto-fit, và grid-template-areas thành phản xạ, phần lớn layout chỉ còn vài dòng CSS trung thực}.

Keep the demo open while you build {Cứ mở demo trong lúc code}: /tools/css-grid-complete-demo/.