Skip to content

CSS Grid 布局应用说明

这篇文章解决什么问题

CSS Grid 经常被一句话概括成“二维布局”,但真实项目里,它解决的不只是“横向加纵向”。

它更适合解决这类问题:

  • 页面有明确的行和列,比如后台工作台、卡片墙、图片墙。
  • 布局要对齐多列内容,不能只靠 marginwidth 硬凑。
  • 卡片数量不固定,但希望自动换行、自动填满空间。
  • 左侧导航、主内容、右侧信息栏需要稳定排布。
  • 表单要做标签列、内容列、按钮列的对齐。
  • 有些元素要跨行或跨列,比如统计卡、主图、推荐位。

本文会从 Grid 的心智模型开始,横向对比 Flex,再讲常用属性、真实场景和容易写错的地方。

先说结论

CSS Grid 的核心价值是:

先定义一个网格,再把子元素放进这个网格。

最小可用写法:

css
.card-list {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 16px;
}

这表示:

  • .card-list 是 Grid 容器。
  • 它有 3 列。
  • 每列平分剩余空间。
  • 子元素之间有 16px 间距。

如果要做响应式卡片列表,常见写法是:

css
.card-list {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 16px;
}

这段代码的意思是:

  • 每个卡片最小 240px
  • 空间足够时自动增加列数。
  • 空间不够时自动换行。
  • 每列最大可以撑满剩余空间。

这是 Grid 在业务项目里最常见、最实用的写法之一。

Grid 和 Flex 怎么选

很多人学 Grid 时,第一个问题不是语法,而是:什么时候用 Grid,什么时候用 Flex?

简单判断:

  • 一维排列,用 Flex。
  • 二维布局,用 Grid。
  • 内容自己排一行或一列,用 Flex。
  • 你先设计好行列结构,再放内容,用 Grid。

对比表:

能力FlexGrid
核心方向一维二维
主要关注主轴和交叉轴行和列
适合内容导航、按钮组、横向列表、居中页面骨架、卡片墙、表单、仪表盘
换行后对齐不擅长跨行列对齐擅长
子项跨列不直观grid-column 很自然
布局控制权内容驱动更多容器规划更多

示例:

css
.toolbar {
  display: flex;
  align-items: center;
  gap: 8px;
}

工具栏是一行内容,用 Flex 更直接。

css
.dashboard {
  display: grid;
  grid-template-columns: 240px 1fr 320px;
  gap: 24px;
}

后台页面有左中右三块区域,用 Grid 更清晰。

不要把它们看成互相替代。真实项目里,经常是外层 Grid,内层 Flex:

css
.page {
  display: grid;
  grid-template-columns: 240px 1fr;
}

.toolbar {
  display: flex;
  justify-content: space-between;
  align-items: center;
}

Grid 的基本模型

Grid 有两个核心角色:

  • Grid 容器:写 display: grid 的元素。
  • Grid 子项:容器的直接子元素。
html
<div class="grid">
  <div>1</div>
  <div>2</div>
  <div>3</div>
</div>
css
.grid {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  gap: 12px;
}

只有 .grid 的直接子元素会成为 Grid 子项。孙子元素不会自动参与这层 Grid。

html
<div class="grid">
  <article>
    <h3>标题</h3>
    <p>内容</p>
  </article>
</div>

这里 article 是 Grid 子项,h3p 不是这层 Grid 的子项。

如果 article 内部也要用 Grid,需要给 article 单独写:

css
.grid article {
  display: grid;
}

定义列:grid-template-columns

grid-template-columns 用来定义列。

固定列:

css
.layout {
  display: grid;
  grid-template-columns: 240px 1fr;
}

这表示左列固定 240px,右列占剩余空间。

三等分:

css
.cards {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
}

更常写成:

css
.cards {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
}

混合宽度:

css
.page {
  display: grid;
  grid-template-columns: 220px minmax(0, 1fr) 320px;
}

这里中间列用了 minmax(0, 1fr),原因是实际业务里中间内容可能有长文本、表格、代码块。如果只写 1fr,有时内容最小宽度会把列撑开,导致页面横向溢出。

fr 是什么

fr 表示 Grid 中的剩余空间份额。

css
.grid {
  display: grid;
  grid-template-columns: 1fr 2fr;
}

如果容器扣掉间距后还有 900px,第一列大约 300px,第二列大约 600px

fr 不是百分比。它会在 Grid 算完固定尺寸、间距、内容约束后,再分配剩余空间。

对比:

css
.grid-a {
  grid-template-columns: 50% 50%;
  gap: 16px;
}

这个写法容易溢出,因为两列已经占满 100%,再加 gap 就超过容器宽度。

更稳:

css
.grid-b {
  grid-template-columns: 1fr 1fr;
  gap: 16px;
}

fr 会把 gap 扣掉之后再分配空间。

定义行:grid-template-rows

grid-template-rows 用来定义行。

css
.panel {
  display: grid;
  grid-template-rows: auto 1fr auto;
  min-height: 480px;
}

这个结构常用于面板:

  • 头部高度由内容决定。
  • 中间区域占剩余空间。
  • 底部高度由内容决定。

示例:

html
<section class="panel">
  <header>标题</header>
  <main>内容</main>
  <footer>操作按钮</footer>
</section>
css
.panel {
  display: grid;
  grid-template-rows: auto minmax(0, 1fr) auto;
  min-height: 480px;
}

中间行写 minmax(0, 1fr),也是为了避免内容最小高度把布局撑开。

间距:gap

Grid 里不要优先用子元素 margin 做网格间距。用 gap 更干净。

css
.cards {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 16px;
}

也可以分开写:

css
.cards {
  row-gap: 24px;
  column-gap: 16px;
}

gap 的好处:

  • 不需要处理第一项、最后一项的额外 margin。
  • 行间距和列间距语义清楚。
  • 配合 fr 时不会产生额外溢出。

响应式卡片:repeat()minmax()auto-fit

这组写法非常常见:

css
.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 16px;
}

逐段理解:

css
minmax(240px, 1fr)

表示每列最小 240px,最大占一份剩余空间。

css
repeat(auto-fit, ...)

表示容器能放几列就放几列。

这个写法适合:

  • 商品列表
  • 文章卡片
  • 图片墙
  • 资源列表
  • 功能入口

示例:

html
<div class="card-grid">
  <article>卡片 1</article>
  <article>卡片 2</article>
  <article>卡片 3</article>
  <article>卡片 4</article>
</div>
css
.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 16px;
}

如果你希望列数完全由断点控制,也可以写媒体查询:

css
.card-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 16px;
}

@media (min-width: 768px) {
  .card-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (min-width: 1200px) {
  .card-grid {
    grid-template-columns: repeat(4, 1fr);
  }
}

两种方式都合理:

  • 内容卡片最小宽度明确,用 auto-fit + minmax()
  • 设计稿强依赖固定断点,用 media query。

auto-fitauto-fill

auto-fitauto-fill 看起来很像,但空列处理不同。

css
.grid-fit {
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
}

.grid-fill {
  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
}

简单理解:

写法空列怎么处理常见用途
auto-fit折叠空列,让已有项拉伸卡片列表、商品列表
auto-fill保留空列轨道需要稳定网格轨道的场景

大多数业务卡片列表,优先用 auto-fit

原因是用户通常希望已有卡片撑满当前行,而不是右侧保留看不见的空列。

子项跨列和跨行

Grid 子项可以跨列:

css
.featured-card {
  grid-column: span 2;
}

也可以指定起止线:

css
.hero {
  grid-column: 1 / 3;
}

表示从第 1 条列线开始,到第 3 条列线结束。也就是跨 2 列。

跨行:

css
.tall-card {
  grid-row: span 2;
}

示例:

css
.magazine-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 16px;
}

.magazine-grid .main-story {
  grid-column: span 2;
  grid-row: span 2;
}

适合:

  • 推荐位
  • 数据大卡
  • 主图区域
  • 杂志式布局

注意:跨行布局要确认内容高度,否则容易出现视觉空洞。卡片瀑布流不是 Grid 的强项,复杂瀑布流通常需要额外策略。

命名区域:grid-template-areas

如果页面骨架很明确,可以用命名区域。

html
<div class="app-shell">
  <header class="header">Header</header>
  <aside class="sidebar">Sidebar</aside>
  <main class="main">Main</main>
  <aside class="panel">Panel</aside>
</div>
css
.app-shell {
  min-height: 100vh;
  display: grid;
  grid-template-columns: 240px minmax(0, 1fr) 320px;
  grid-template-rows: auto minmax(0, 1fr);
  grid-template-areas:
    "header header header"
    "sidebar main panel";
}

.header {
  grid-area: header;
}

.sidebar {
  grid-area: sidebar;
}

.main {
  grid-area: main;
}

.panel {
  grid-area: panel;
}

优点:

  • 页面结构一眼能看出来。
  • 改布局时比一堆 grid-column 更直观。
  • 适合后台壳、文档壳、管理台布局。

响应式时也很清楚:

css
@media (max-width: 900px) {
  .app-shell {
    grid-template-columns: 1fr;
    grid-template-rows: auto auto minmax(0, 1fr) auto;
    grid-template-areas:
      "header"
      "sidebar"
      "main"
      "panel";
  }
}

缺点是:区域必须是矩形,不能写成断裂形状。

错误示例:

css
grid-template-areas:
  "a a b"
  "a c c";

这里 a 不是一个完整矩形,布局无效。

对齐:justifyalign

Grid 对齐有两层:

  • 对齐单个子项。
  • 对齐整个网格。

子项在自己格子里怎么对齐:

css
.grid {
  display: grid;
  justify-items: center;
  align-items: center;
}

含义:

  • justify-items 控制水平方向。
  • align-items 控制垂直方向。

单个子项也可以覆盖:

css
.item-special {
  justify-self: end;
  align-self: start;
}

整个网格在容器里怎么对齐:

css
.grid {
  display: grid;
  width: 800px;
  height: 400px;
  grid-template-columns: repeat(3, 160px);
  justify-content: center;
  align-content: center;
}

含义:

  • justify-content 控制整组列在容器水平方向的位置。
  • align-content 控制整组行在容器垂直方向的位置。

简单记:

  • items 管格子里的项目。
  • content 管整个网格。
  • justify 多数时候看横向。
  • align 多数时候看纵向。

自动布局:grid-auto-flow

Grid 默认按行自动放置子项:

css
.grid {
  display: grid;
  grid-auto-flow: row;
}

也可以按列:

css
.grid {
  display: grid;
  grid-auto-flow: column;
}

还有一个常见但要谨慎的值:

css
.grid {
  grid-auto-flow: dense;
}

dense 会尝试把后面的元素填进前面的空洞。

它适合图片墙、视觉卡片墙,但不适合强阅读顺序的内容。因为视觉顺序可能和 DOM 顺序不完全一致,读屏和键盘访问仍按 DOM 顺序走。

所以文章列表、表单、流程步骤这类有明确顺序的内容,不要随便用 dense

常见场景一:后台三栏布局

html
<div class="admin-page">
  <aside class="admin-nav">导航</aside>
  <main class="admin-main">主内容</main>
  <aside class="admin-panel">信息栏</aside>
</div>
css
.admin-page {
  min-height: 100vh;
  display: grid;
  grid-template-columns: 240px minmax(0, 1fr) 320px;
  gap: 24px;
}

重点是中间列:

css
minmax(0, 1fr)

这能减少表格、代码块、长文本把主内容列撑爆的概率。

如果移动端要堆叠:

css
@media (max-width: 960px) {
  .admin-page {
    grid-template-columns: 1fr;
  }
}

常见场景二:表单对齐

表单里经常需要标签和输入框对齐。

html
<form class="settings-form">
  <label for="name">名称</label>
  <input id="name" />
  <label for="email">邮箱</label>
  <input id="email" />
</form>
css
.settings-form {
  display: grid;
  grid-template-columns: 120px minmax(0, 1fr);
  gap: 16px 20px;
  align-items: center;
}

如果有按钮区域,可以让它从第二列开始:

css
.form-actions {
  grid-column: 2;
}

这种写法比给每个标签手写宽度、再用 margin 调输入框更稳定。

常见场景三:自适应卡片列表

html
<section class="article-grid">
  <article>文章 1</article>
  <article>文章 2</article>
  <article>文章 3</article>
</section>
css
.article-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 20px;
}

这个模板适合大多数列表页。你只需要改两个值:

  • 260px:卡片允许的最小宽度。
  • 20px:卡片间距。

如果卡片内还有图片,可以再配合 aspect-ratio

css
.article-card-cover {
  aspect-ratio: 16 / 9;
  overflow: hidden;
}

Grid 负责整体排布,aspect-ratio 负责单张卡片里的媒体比例。

常见场景四:九宫格和固定区域

css
.shortcut-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 12px;
}

如果要固定每个格子的比例:

css
.shortcut-item {
  aspect-ratio: 1 / 1;
  display: grid;
  place-items: center;
}

place-items: center 是简写,等价于:

css
align-items: center;
justify-items: center;

它适合图标入口、功能宫格、上传占位框。

常见场景五:主次卡片布局

html
<section class="news-grid">
  <article class="lead">主新闻</article>
  <article>新闻 2</article>
  <article>新闻 3</article>
  <article>新闻 4</article>
</section>
css
.news-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 16px;
}

.news-grid .lead {
  grid-column: span 2;
  grid-row: span 2;
}

这种布局用 Flex 会很别扭,用 Grid 更符合结构。

如果小屏改成单列:

css
@media (max-width: 768px) {
  .news-grid {
    grid-template-columns: 1fr;
  }

  .news-grid .lead {
    grid-column: auto;
    grid-row: auto;
  }
}

跨行跨列的规则,在小屏经常要重置,否则容易出现奇怪空位。

常见坑

只想排一行按钮,却用了 Grid

css
.actions {
  display: grid;
  grid-template-columns: repeat(3, auto);
}

按钮组通常用 Flex 更自然:

css
.actions {
  display: flex;
  align-items: center;
  gap: 8px;
}

不要为了用 Grid 而用 Grid。

1fr 被内容撑爆

css
.layout {
  display: grid;
  grid-template-columns: 240px 1fr;
}

如果右侧有表格、长链接、代码块,可能横向溢出。

更稳:

css
.layout {
  display: grid;
  grid-template-columns: 240px minmax(0, 1fr);
}

Grid 项内部也可能需要:

css
.main {
  min-width: 0;
}

用百分比列再加 gap

css
.grid {
  display: grid;
  grid-template-columns: 50% 50%;
  gap: 16px;
}

这可能溢出。优先:

css
.grid {
  grid-template-columns: 1fr 1fr;
  gap: 16px;
}

grid-column 写在非直接子元素上

html
<div class="grid">
  <article>
    <h3 class="title">标题</h3>
  </article>
</div>
css
.title {
  grid-column: span 2;
}

这里不会影响外层 Grid,因为 .title 不是 .grid 的直接子项。

应该写在 article 上:

css
.grid article {
  grid-column: span 2;
}

或者让 article 自己也成为 Grid 容器。

过度依赖 grid-template-areas

命名区域适合页面骨架,但不适合所有组件。

如果只是普通卡片列表:

css
.cards {
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
}

没必要写很多区域名。区域名越多,维护成本越高。

忽略 DOM 顺序

Grid 可以改变视觉位置,但不要把它当成改内容顺序的工具。

css
.important {
  grid-column: 1;
  grid-row: 1;
}

视觉上它到了前面,但 DOM 顺序没有变。读屏、键盘焦点、复制顺序仍然按 DOM 走。

重要内容顺序应该先由 HTML 决定,再由 CSS 做布局。

实战模板

自适应卡片列表

css
.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 16px;
}

固定侧栏 + 自适应内容

css
.page-layout {
  display: grid;
  grid-template-columns: 260px minmax(0, 1fr);
  gap: 24px;
}

头中尾面板

css
.panel {
  min-height: 420px;
  display: grid;
  grid-template-rows: auto minmax(0, 1fr) auto;
}

表单标签对齐

css
.form-grid {
  display: grid;
  grid-template-columns: 120px minmax(0, 1fr);
  gap: 16px 20px;
  align-items: center;
}

居中内容

css
.empty-state {
  min-height: 240px;
  display: grid;
  place-items: center;
}

主次卡片

css
.feature-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 16px;
}

.feature-grid .primary {
  grid-column: span 2;
  grid-row: span 2;
}

总结

CSS Grid 最适合处理有明确行列关系的布局。

记住几个判断:

  • 一维内容优先 Flex。
  • 二维结构优先 Grid。
  • 卡片列表常用 repeat(auto-fit, minmax(..., 1fr))
  • 页面骨架常用 grid-template-columnsgrid-template-areas
  • 中间弹性列常用 minmax(0, 1fr)
  • 网格间距优先用 gap
  • 跨列跨行用 grid-column / grid-row
  • 不要用 Grid 改变真实内容顺序。

真实项目里,不要先背属性。先看布局是不是已经有明确的行和列。如果有,Grid 通常就是更干净的方案。

基于 VitePress 的个人知识库骨架