← 返回文章

CSS / 响应式设计 / Grid

CSS Subgrid 实战:让嵌套组件真正对齐父级网格

CSS Grid 很擅长控制一层布局,但只要卡片内部再建一个 Grid,父子网格就会各算各的轨道。标题长短不同、说明文字行数不同,最后每张卡片的按钮都停在不同高度。开发者通常会加固定高度、额外 wrapper,甚至用 JavaScript 测量尺寸。

Subgrid 让嵌套网格直接复用父网格的轨道。子组件不再猜测父级列宽,也不需要复制一份 grid-template-columns。它尤其适合表单标签、卡片内容和多层数据表这类需要跨组件对齐的场景。

一、普通嵌套 Grid 为什么会错位

下面的表单外层有两列,但每个字段又是独立网格:

1
2
3
4
5
6
7
8
9
10
11
<form class="profile-form">
<div class="field">
<label for="name">姓名</label>
<input id="name" name="name" />
</div>

<div class="field">
<label for="company">公司名称</label>
<input id="company" name="company" />
</div>
</form>
1
2
3
4
5
6
7
8
9
10
11
.profile-form {
display: grid;
grid-template-columns: 8rem minmax(0, 1fr);
gap: 16px 24px;
}

.field {
display: grid;
grid-template-columns: 8rem minmax(0, 1fr);
grid-column: 1 / -1;
}

虽然当前数值相同,但父子列并没有真正关联。父级断点改成 10rem 1fr 时,字段组件也必须同步修改。组件嵌套更深后,同一份轨道定义会散落在多个文件里。

二、用 subgrid 继承父级轨道

.field 的列定义改成 subgrid

1
2
3
4
5
6
7
8
9
10
11
.profile-form {
display: grid;
grid-template-columns: minmax(8rem, 12rem) minmax(0, 1fr);
gap: 16px 24px;
}

.field {
display: grid;
grid-template-columns: subgrid;
grid-column: 1 / -1;
}

.field 跨越父级的两条列轨道,因此它的子元素会直接落到这两列中。MDN 对 Subgrid 的说明是:当 grid-template-columnsgrid-template-rows 或两者使用 subgrid 时,嵌套网格会使用父网格定义的轨道,而不是创建新的轨道列表。

这里有两个前提:

  1. 子元素本身必须是 Grid 容器。
  2. 子元素要在父网格中跨越相应数量的轨道。

如果 .field 只占父级一列,subgrid 也只能得到那一列,不会凭空继承整张网格。

三、只继承列,行仍可独立布局

Subgrid 可以只作用于一个轴:

1
2
3
4
5
6
7
8
9
10
.field {
display: grid;
grid-template-columns: subgrid;
grid-template-rows: auto auto;
grid-column: 1 / -1;
}

.field__error {
grid-column: 2;
}

列使用父网格,错误提示在第二行自行排列。这样每个输入框仍和全局表单列对齐,局部错误状态又不需要父级提前知道高度。

同理,卡片列表可以继承行轨道,让标题、摘要、价格和按钮跨卡片对齐:

1
2
3
4
5
6
7
8
9
10
11
12
.pricing-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
grid-template-rows: auto auto auto 1fr auto;
gap: 24px;
}

.pricing-card {
display: grid;
grid-template-rows: subgrid;
grid-row: span 5;
}

每张卡片跨越五条父级行轨道,内部五个区域使用相同的行尺寸。最长标题决定标题行高度,所有卡片的后续内容随之对齐,不再依赖固定高度。

四、gap 会继承,也可以覆盖

子网格默认使用父网格对应轴的间距。需要局部调整时,可以重新声明:

1
2
3
4
5
6
.field {
display: grid;
grid-template-columns: subgrid;
grid-column: 1 / -1;
column-gap: 32px;
}

覆盖 gap 不会改变父级轨道本身,只会影响子网格内容在轨道之间的间距表现。为了避免对齐看似差几个像素,迁移时要检查旧组件是否在 padding、margin 和 gap 上重复留白。

五、命名网格线让组件契约更清楚

父网格可以给轨道线命名:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
.profile-form {
display: grid;
grid-template-columns:
[label-start] minmax(8rem, 12rem)
[control-start] minmax(0, 1fr)
[control-end];
}

.field {
display: grid;
grid-template-columns: subgrid;
grid-column: label-start / control-end;
}

.field__help {
grid-column: control-start / control-end;
}

相比 grid-column: 2control-start 更像公开的布局接口。页面级容器负责定义“标签区”和“控件区”,字段组件只消费这些位置。

命名也不要过度。对于只有两列的简单卡片,数字轨道已经足够清楚;在跨多个组件共享的复杂网格中,命名才会明显降低维护成本。

六、响应式布局中的用法

移动端可以回到单列,宽屏再启用父级列:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
.profile-form {
display: grid;
gap: 12px;
}

.field {
display: grid;
gap: 6px;
}

@media (min-width: 48rem) {
.profile-form {
grid-template-columns: 12rem minmax(0, 1fr);
gap: 16px 24px;
}

.field {
grid-template-columns: subgrid;
grid-column: 1 / -1;
}
}

这里的媒体查询控制页面级结构,Subgrid 负责让嵌套字段服从结构。它和容器查询也可以组合:外层容器决定何时变成两列,内层 Subgrid 继承实际生成的列轨道。

七、渐进增强与回退

在需要兼容旧浏览器时,先提供可用的普通布局,再通过特性查询增强:

1
2
3
4
5
6
7
8
9
10
11
.field {
display: grid;
grid-template-columns: 10rem minmax(0, 1fr);
}

@supports (grid-template-columns: subgrid) {
.field {
grid-template-columns: subgrid;
grid-column: 1 / -1;
}
}

回退不一定要做到像素完全一致,只要标签、输入和错误信息仍然可读可操作即可。浏览器支持情况应根据项目真实用户数据判断,而不是只看开发机。

不要为了跨层对齐就随意使用 display: contents 删除中间盒子。它可能改变布局、事件定位和无障碍树表现。Subgrid 能在保留组件语义与盒模型的同时共享轨道,通常是更直接的选择。

八、常见踩坑

  • 子网格没有跨越父级多条轨道,却期待继承完整网格。
  • 父级不是 Grid 容器,subgrid 自然无轨道可继承。
  • 只设置 grid-template-columns: subgrid,却忘记 .fielddisplay: grid
  • 内容有长链接或不可换行文本,仍会撑开轨道;需要配合 minmax(0, 1fr)min-inline-size: 0 和换行策略。
  • 为了所有卡片等高使用大量空白行,反而让内容关系变得难懂。

九、上线前检查清单

  1. 子组件需要对齐的是父级列、父级行,还是两个轴?
  2. 子网格是否跨越了正确的轨道数量?
  3. gap、padding 和 margin 是否重复计算?
  4. 长标题、错误文本和多语言内容是否会撑破布局?
  5. 小屏单列状态是否仍然符合阅读顺序?
  6. 不支持 Subgrid 时是否有可用回退?

Subgrid 的意义不是少写一行轨道定义,而是让嵌套组件真正参与同一张布局系统。父级负责分配空间,子级保留语义并共享轨道;页面结构调整时,组件自然跟随,不再依赖复制数值和手工同步。

参考资料