CSS Grid 是一套二维布局系统。你可以同时规划列和行,再把元素放进网格线围成的区域。页面骨架、仪表盘、卡片墙这类需要横纵对齐的界面,都很适合用 Grid。它的思路是先定好空间规则,再安排内容的位置。
我们先把术语、轨道尺寸和响应式模板说清楚,再看显式与隐式网格、自动放置、对齐、重叠、盒模型和子网格。语法不用一次背完。遇到布局问题时,先问自己三件事:网格线在哪里,轨道怎样计算,元素为什么落在这里。
读代码时,可以把斜杠前后看成“起点 / 终点”,把轨道看成“两条相邻网格线之间的空间”。先找线,再看元素放在哪一段,很多 Grid 规则就容易理解了。
给容器设置 display: grid,得到的是块级网格容器;设置 display: inline-grid,得到的是参与行内排版的网格容器。无论选哪一种,只有容器的直接子元素会成为网格项。更深层的后代不会自动加入这张网格,不过你仍然可以把某个网格项设成新的网格容器。
.dashboard {
display: grid;
}
.inline-badges {
display: inline-grid;
grid-auto-flow: column;
}容器里的匿名文本也会参与网格排版,但它没有可单独选中的元素盒,放置和对齐都不好控制。实际项目中,最好用合适的语义元素把文本包起来。
还有几条边界规则需要记住。网格项上的 float、clear 和 vertical-align 不参与网格放置;网格容器与子项之间不会发生外边距折叠。多栏布局属性不会把网格容器本身变成多栏,::first-line 和 ::first-letter 也不用于网格容器。浮动元素同样不会像在普通块盒里那样侵入网格内部。
<section class="dashboard">
<header>概览</header>
<main>数据</main>
<aside>筛选</aside>
</section>理解 Grid,可以先从网格线开始。两条相邻列线之间是一条列轨道,两条相邻行线之间是一条行轨道。四条线围成的最小矩形叫网格单元格,相邻的一个或多个单元格可以组成矩形的网格区域。元素放进区域后就成了网格项,它可以只占一个单元格,也可以跨过多条轨道,甚至和别的网格项重叠。

定义 3 条列轨道时,会得到 4 条列线。网格线从 1 开始正向编号,也可以从末端用 -1 反向编号。这里容易混淆的一点是,负数只参照显式网格的末端,之后自动生成的隐式轨道不在倒数范围内。
.layout {
display: grid;
grid-template-columns:
[page-start] 16rem
[content-start] 1fr
[content-end] 12rem
[page-end];
}方括号用来声明线名。一条线可以有多个名称,行线和列线也可以同名,因为两者属于不同的轴。线名重复并不会报错;放置元素时,card 2 指的是第二条名为 card 的线。
grid-template-columns 和 grid-template-rows 用来定义显式轨道。长度和百分比得到的轨道相对固定,内容变化不会改变它们,不过百分比仍会跟着容器尺寸变化。列百分比参照容器的内联尺寸;行百分比要生效,容器必须有可确定的块尺寸。比如三行写成 3rem 100% 2rem,中间一行已经占满容器高度,加上首尾两行后,轨道总高度一定会超出容器。
.fixed {
display: grid;
grid-template-columns: 14rem 50% 8rem;
}fr 分配的是固定轨道、内容约束和间隙占用之后剩下的空间。写成 15rem 1fr 10% 时,浏览器先算出 15rem 和 10%,再把余下空间交给中间列;写成 1fr 2fr 1fr,则按 1:2:1 分配可用的弹性空间。不要把 fr 当成百分比的另一种写法,特别是同时存在固定尺寸和内容最小尺寸时,计算结果会有明显差别。
minmax(最小值, 最大值) 给轨道划定一个尺寸范围。fr 可以放在最大值的位置,不能放在最小值的位置。如果最大值小于最小值,浏览器会忽略最大值,按最小值处理。常见的 minmax(0, 1fr) 允许轨道缩到 0,可以避免内容的自动最小尺寸把布局撑宽。minmax(12rem, 1fr) 则保留 12rem 的下限,并继续参与弹性空间分配。
.shell {
display: grid;
grid-template-columns: 15rem minmax(0, 1fr) minmax(12rem, 20rem);
}
min-content 会把轨道尽量压缩到内容无法再合理断行的宽度,max-content 则尽量让内容保持不换行。它们计算的是整条轨道,所以同一列里要求最宽的内容会影响这一列的尺寸。auto 的最小侧接近元素的自动最小尺寸,最大侧接近 max-content;使用 align-content 或 justify-content 时,auto 轨道还可能被拉伸。
你可以把 fit-content(30rem) 理解成:内容较短时按内容收缩,内容较长时尽量不超过 30rem,同时仍要容纳无法继续压缩的最小内容。因此,如果一张图片本身宽于 30rem,又不允许缩小,它仍可能把轨道撑过这个上限。
repeat() 可以重复单条轨道,也可以重复一组轨道和线名。它能和普通轨道写在一起,但内部不能再嵌套另一个 repeat()。
.calendar {
display: grid;
grid-template-columns: repeat(7, [day-start] minmax(0, 1fr));
}
.pattern {
grid-template-columns: repeat(3, 2rem 1fr 1fr) 2rem;
}auto-fill 和 auto-fit 会根据可用空间决定重复多少次,两者都至少建立一份模式。差别出现在容器还能放下更多轨道、网格项却不够多的时候:auto-fill 会保留空轨道,auto-fit 会把空轨道压缩到 0,让已有轨道继续扩展。即使轨道被压缩,相应的网格线仍然存在,所以不能简单理解成一个生成得多、一个生成得少。
.cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(100%, 16rem), 1fr));
gap: 1rem;
}
自动重复有几项语法限制。同一轴的模板里只能出现一个自动重复,重复模式也需要一个可以计算的固定轨道基准,因此不能直接写 repeat(auto-fit, 1fr)。minmax(16rem, 1fr) 可以使用,是因为浏览器能根据固定的最小值 16rem 判断容器放得下几列。轨道之间需要空隙时,直接用 gap,不用重复空白轨道来模拟。
在 grid-template-areas 中,每个字符串代表一行,每个标识符代表一个单元格。相邻的同名单元格会合并成一个区域,但这个区域必须是矩形。L 形区域或彼此断开的同名片段都会让整条声明失效。句点 . 表示未命名的空单元格,连续写一个或多个句点,效果相同。
.page {
display: grid;
grid-template-columns: 14rem minmax(0, 1fr) 12rem;
grid-template-rows: auto 1fr auto;
grid-template-areas:
"header header header"
"nav main aside"
"footer footer footer";
}
.page > header { grid-area: header; }
每行字符串必须包含相同数量的单元格。代码中的换行本身没有语义,真正划分网格行的是一对对引号。区域模板会创建轨道,但轨道尺寸仍要配合列模板和行模板来指定。如果尺寸列表比区域矩阵更长,多出来的轨道会接在命名区域后面。
声明命名区域时,浏览器还会隐式创建 名称-start 和 名称-end 网格线。反过来,如果四个方向上都有匹配的 content-start 与 content-end 线,也可能形成名为 content 的隐式区域。不过,只要漏掉某些线,区域就可能被推到隐式网格,结果很难预判。实际开发中,更稳妥的做法是先声明区域,让对应线名自动生成。线名与区域名也不要刻意设成一样,以免名称解析发生冲突。
控制四条边界的长属性分别是 grid-column-start、grid-column-end、grid-row-start 和 grid-row-end。实际写代码时,我们更常用 grid-column 与 grid-row:斜杠前是起始线,斜杠后是结束线。只写一个数字时,结束端会取 auto,通常跨一条轨道。写区域名时,浏览器会寻找对应的 -start 和 -end 线。
.hero {
grid-column: 1 / -1;
grid-row: 1 / span 2;
}
.summary {
grid-column: content-start / content-end;
}span 3 表示跨越三条轨道。已经确定起点时,浏览器会朝结束方向查找;已经确定终点、起点写成 span 3 时,则会反向查找。span 只能搭配正整数,省略数字就相当于 span 1。遇到重复线名时,span 2 card 计数的是同名线:浏览器会沿放置方向找到第二条名为 card 的线,并非任意两条普通线。
grid-area 既可以接收区域名,也可以一次写出四条线,顺序是 行起点 / 列起点 / 行终点 / 列终点。这个顺序不是常见的上、右、下、左。读的时候,可以先用前两个值确定左上角,再用后两个值确定右下角。
.notice {
grid-area: 2 / 2 / 4 / 5;
}浏览器会修正部分放置错误。起始线落在结束线之后时,两者会交换;起点和终点都写 span 时,结束端会退回 auto;只有一个无法锚定的命名 span 时,会退成 span 1。这些回退规则适合帮助浏览器容错,不适合当成日常写法。调试时,直接在开发者工具中显示线号会更清楚。
模板属性创建的是显式网格。如果元素被放到显式边界之外,或者自动放置的项目超过了现有单元格,浏览器就会补出隐式线和隐式轨道。隐式轨道的默认尺寸是 auto,通常只够包住内容,所以它看起来可能和固定尺寸的显式轨道不一致。
.queue {
display: grid;
grid-template-columns: repeat(3, 8rem);
grid-auto-rows: minmax(4rem, auto);
grid-auto-columns: 8rem;
}
.queue > .outside {
grid-column: 5; /* 触发隐式第 4、5 列相关的线与轨道 */
}grid-auto-rows 和 grid-auto-columns 用来控制新建的隐式轨道。它们可以接收循环的尺寸序列。例如,grid-auto-rows: 4rem 6rem 会让后续隐式行按 4rem、6rem 交替出现。如果用一个不存在的命名线搜索跨度,算法会继续向隐式方向查找,并把需要的隐式线视为拥有该名称。这也解释了为什么线名拼错后,页面上有时会出现一大片空白。
负编号只从显式网格的末端开始倒数。因此,grid-column: 1 / -1 会覆盖全部显式列,却不会自动包含边界之外的隐式列。页面骨架需要保持稳定时,关键轨道应当明确写进模板;隐式网格更适合承接数量暂时无法确定的重复内容。
没有完整指定行列位置的项目,会交给自动放置算法处理。默认值 grid-auto-flow: row 会按行寻找能够容纳项目的区域;改成 column 后,算法按列查找。位置已经明确的项目会先占据空间,自动放置的项目再绕开它们。
.gallery {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
grid-auto-rows: 6rem;
grid-auto-flow: row;
}
.gallery > .wide { grid-column: span 2; }
.gallery > .tall { grid-row网格算法安排的是项目占据的网格区域,不会根据元素的视觉内容自动调整跨度。如果图片很大,又没有声明合适的跨度,图片就可能溢出并盖住相邻内容。处理时先用 max-width: 100% 约束图片,再用跨度说明它应该占多少空间。
加上 dense 后,算法会回头扫描之前留下的空位,把后面较小的项目填进去。这样能减少空洞,却也可能让视觉顺序偏离 DOM 顺序,键盘焦点和屏幕阅读器的朗读顺序便会与画面不一致。图库、标签墙这类内容顺序不重要的场景,可以考虑使用密集模式;关键内容不要靠它重排。
row-gap、column-gap 和简写 gap 会在轨道之间留出间隙,但不会自动给容器外缘增加空白。gap: 1rem 2rem 的第一个值控制行间隙,第二个值控制列间隙。间隙会参与可用空间计算,所以 fr 分到的是扣除间隙后剩下的空间。
看 Grid 的对齐属性时,先分清两个问题:项目在自己的区域里怎么摆,整组轨道在容器里怎么摆。justify-* 作用于内联轴,align-* 作用于块轴。不要只记成水平和垂直,因为书写模式改变后,轴向也会跟着改变。
项目的默认对齐通常是 stretch,不过只有对应轴的尺寸为 auto 时才能拉伸。改用 start、end 或 center 后,没有设定尺寸的项目会收缩到包住内容。baseline 和 last baseline 可以让同一轨道内的文字按基线对齐。self-start 参照项目自身的书写方向,因此和参照容器方向的 start 不完全一样。
只有轨道总尺寸小于容器时,justify-content 和 align-content 才有剩余空间可以分配。space-between 等值调整的是轨道之间的空间,不会改变项目在单元格内部的对齐。stretch 也只会拉伸允许拉伸的轨道,固定尺寸轨道不会因此缩小。
.board {
display: grid;
gap: 1rem 1.5rem;
place-items: center start;
place-content: space-between center;
}
.board > .featured {
place-self: stretch;
}多个网格项可以放进同一个单元格,这可能是有意设计的重叠效果,并不一定是布局错误。默认绘制顺序与文档顺序一致,后出现的项目会盖住先出现的项目。你也可以用 z-index 明确层叠顺序,而且网格项即使没有设置 position,也能用 z-index 参与层叠。
.poster {
display: grid;
}
.poster > img,
.poster > figcaption {
grid-area: 1 / 1;
}
.poster > figcaption {
align-self: end;
z-index: 1;
}order 会改变自动放置的顺序,也会影响同一层级发生重叠时的绘制顺序。不过,它不会改变 DOM 顺序、屏幕阅读器的朗读顺序或键盘导航顺序。如果源码顺序可以调整,优先调整源码。只有视觉顺序确实需要和语义顺序不同时,才考虑使用 order。
网格项通过外边距边缘贴合网格区域。正外边距会把可见盒向内推,负外边距可以把它拉出区域,但项目的外边距不会参与轨道内容尺寸的计算。固定宽高加上四边距仍无法填满区域时,布局会根据轴向约束调整末端外边距。自动外边距会吸收剩余空间,因此 margin-inline-start: auto 可以把项目推到内联轴末端,四边都设为 auto 则能在双轴居中。
绝对定位的项目也可以用网格线定义定位包含块。先用 grid-area 选中一块区域,再用 inset 在区域内部偏移即可。它不会参与轨道尺寸计算。如果绝对定位项目某一侧的网格线是 auto,这一侧可能以网格容器的内边距边缘为定位边界,并不会像普通自动放置那样默认跨一条轨道。
grid 简写有两种用法:一种同时声明区域、显式行和显式列,另一种声明自动流与自动轨道。这两套语法不能混在同一条简写中。更容易踩坑的是,没有写到的相关属性会被重置为初始值。因此,代码里通常先写 grid,再用长属性补充或覆盖具体设置。
.app {
display: grid;
grid:
"head head" 4rem
"nav main" minmax(0, 1fr)
"foot foot" auto
/ 15rem minmax(0, 1fr);
gap: 1rem;
}上面的代码等价于分别声明 grid-template-areas、grid-template-rows 和 grid-template-columns。不使用命名区域时,斜杠前写行轨道,斜杠后写列轨道:
.simple {
grid: auto 1fr auto / 16rem minmax(0, 1fr);
}自动流形式可以把方向、隐式行和隐式列写在一起,但往往不如长属性直观。团队成员如果不熟悉简写的重置范围,分别声明 grid-auto-flow、grid-auto-rows 和 grid-auto-columns 会更容易维护。
普通的嵌套网格会创建一套自己的轨道。即使父子网格写了相同的尺寸,也不能保证不同组件始终对齐。subgrid 可以让子网格在某一轴上直接使用最近祖先的轨道。子项还会参与共享轨道的尺寸计算,这样页头、正文和页脚就能沿着同一组网格线调整。
.page {
display: grid;
grid-template-columns: repeat(12, minmax(0, 1fr));
gap: 1.25rem;
}
.page > header,
.page > main,
.page > footer {
grid-column: 1 / -1

子网格只会继承声明了 subgrid 的轴,另一个轴仍然可以定义自己的显式轨道。比如,卡片可以在列轴继承页面网格,同时用 grid-template-rows: auto 1fr auto 管理内部的行。进入子网格后,线号会从它自己的覆盖范围重新计算。即使它跨越父网格的第 3 条到第 10 条线,在子网格内部,两端仍然用 1 和 -1 表示。
子网格可以继承祖先的线名,也可以在 subgrid 后面给局部网格线增加新名称。在使用子网格的轴上,它不会自己创建隐式轨道;内容超出时,仍会落进已有轨道的范围。因此,面对数量未知的重复行,通常要在非子网格容器上用 grid-auto-rows 规划。
.cards {
grid-column: 2 / -2;
display: grid;
grid-template-columns: subgrid [cards-start] [] [] [cards-end];
column-gap: 2rem;
}子网格默认继承祖先的间隙,也可以设置自己的 row-gap 或 column-gap。新间隙只影响当前子网格,以及继续继承它的后代,不会反过来改变祖先中的其他区域。display: contents 不能替代子网格,它只是移除中间盒,无法在这一层保留背景、边框和间距,也没有子网格的布局能力。
遇到看不懂的网格布局时,先别急着反复改数值。沿着浏览器的计算顺序检查,通常更快找到原因:
下面把前面的规则放进一套应用外壳:导航保持固定宽度,主内容区可以收缩,页面用命名区域组织,卡片列随空间变化,隐式行也有明确的尺寸范围。
.app {
min-height: 100dvh;
display: grid;
grid-template:
"header header" auto
"nav main" minmax(0, 1fr)
"footer footer" auto
/ 16rem minmax(0, 1fr);
gap: 1rem;
}
学习 Grid,不必把注意力压在某一条属性上。更实用的是记住它的计算顺序:容器先建立网格上下文,网格线划出轨道,固定值、内容约束和弹性单位共同决定轨道尺寸,元素最后通过网格线、命名区域或自动流完成放置。显式网格提供可预测的页面骨架,隐式网格接住数量未知的内容。对齐属性分别处理项目与整组轨道,子网格则让嵌套组件继续沿用祖先的对齐关系。
以后再碰到 Grid 布局问题,可以先标出轨道、区域和项目,再看剩余空间是怎样分配的。沿着这个顺序排查,repeat()、minmax()、命名区域、自动流和子网格就能放回同一套空间模型里理解。