返回文章归档
Field Note

为什么我现在更喜欢用 gap,而不是给列表项硬写 margin

很多布局问题表面上是在调间距,实际上是在和 margin 的副作用打架。本文结合列表、卡片、导航和响应式场景,聊聊我为什么现在更习惯优先用 gap,以及什么时候 margin 还是更合适。

Aa
发布
2026-05-09
阅读
8 分钟
展开文章目录

刚开始写布局时,我很长一段时间都在靠 margin 处理间距。

列表项之间加一点。 卡片之间推开一点。 导航按钮之间隔一点。 看起来都能做,而且写起来也很顺手。

但项目做多了之后,我越来越明显地感觉到一件事:

很多“布局越来越乱”的问题,不是因为 CSS 太难,而是因为我一开始就把“组件自身的外边距”和“容器内部的排布间距”混在了一起。

这也是为什么我现在只要是处理一组元素之间的间距,第一反应通常都会先看一句:

能不能直接用 gap?

margin 不是不能用,而是它经常承担了不该它承担的事

先说清楚一点。

margin 本身当然没有问题。 它依然是 CSS 里非常基础、也非常有用的属性。

问题在于,很多时候我们明明想表达的是:

  • 这一组元素之间要有固定间距
  • 间距由父容器统一控制
  • 第一个和最后一个元素不需要特殊处理

但最后却写成了这样:

css
.list-item {
  margin-right: 16px;
}

或者:

css
.card + .card {
  margin-top: 20px;
}

短期看没问题。 可一旦这个结构开始变化,你很快就会遇到下面这些情况:

  • 最后一个元素多出一截空白
  • 横排改竖排时,原来的 margin-right 全得重写
  • 组件被搬到另一个容器里之后,外边距语义不对了
  • 嵌套布局一多,间距到底是谁控制的会越来越难看清

也就是说,margin 很容易把“排版责任”塞进组件自己身上。

而我现在更想要的是:

组件只管长什么样。 至于它和别人隔多远,尽量交给外层布局来决定。

gap 更像是在描述“这一组元素怎么排”

gap 的好处,不只是少写几行代码。

更重要的是,它表达得更准。

比如这段:

css
.list {
  display: flex;
  gap: 16px;
}

这句话的意思非常清楚:

  • 这是一个 flex 容器
  • 容器里的子项彼此相隔 16px
  • 不需要关心第一个和最后一个元素怎么处理

这其实是在描述“布局关系”。

而不是像 margin 那样,把间距挂在某一个具体元素身上。

这种区别在小 demo 里不明显,但一到真实页面里就特别重要。

因为页面里的结构不是静止的。 它经常会变:

  • 横排变竖排
  • 一行变多行
  • 项目数量变多变少
  • 内容从按钮换成卡片
  • 组件被复用到别的区域

这时候,如果间距是靠容器统一控制的,整个结构会稳很多。

我现在最常用 gap 的几个场景

1. 按钮组

css
.actions {
  display: flex;
  gap: 12px;
}

这个场景几乎没什么悬念。 按钮和按钮之间的距离,本来就应该由这一组按钮的容器来控制。

如果你给每个按钮都写 margin-right,那它一旦换行、反向排列,或者插入一个新按钮,维护成本就会上来。

2. 卡片列表

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

这个比给每张卡片写外边距自然得多。

因为这里真正存在的是“网格之间的间隙”,而不是“某张卡片自己要往外顶 24px”。

尤其在 grid 里,gap 基本就是天选属性。 它和布局模型本身是贴合的。

3. 表单项堆叠

css
.form-section {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

这类纵向结构以前很容易写成:

css
.form-item {
  margin-bottom: 16px;
}

但一旦最后一个元素不想留白,或者中间插入帮助文本、错误提示,你就会开始补各种选择器。

换成 gap 之后,这类问题会少很多。

4. 导航、标签、筛选项

css
.tag-list {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}

这个场景我现在几乎默认先用 gap。

因为一旦允许换行,margin 的控制就会变得很别扭。 横向间距、纵向间距、最后一列、换行后的对齐,都会变得更麻烦。

而 gap 对“换行后的整体观感”通常更友好。

响应式场景里,gap 会省事很多

我后来越来越喜欢 gap,还有一个很现实的原因:

它特别适合做响应式。

比如:

css
.list {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 24px;
}
 
@media (max-width: 768px) {
  .list {
    grid-template-columns: 1fr;
    gap: 16px;
  }
}

这里你只需要改容器。

子项本身完全不用动。

但如果你之前是靠每个子项的 margin 在撑结构,那响应式时经常会变成:

  • 改方向
  • 改边距方向
  • 去掉最后一个间距
  • 调整特殊项
  • 修正嵌套样式

最后就会越来越像在补漏洞。

什么时候我还是会继续用 margin

说了这么多,不代表 margin 就该退场。

我现在的习惯更像是这样:

如果我要表达的是“元素和元素之间的编排间距”,优先想 gap。 如果我要表达的是“某个元素需要和外界保持距离”,那还是用 margin。

比如这些场景,我还是会直接用 margin:

1. 文章段落和标题之间的间距

这是排版的一部分,不一定总能抽出一个专门容器统一控制。

2. 某个独立组件和上下文之间的留白

比如一个卡片模块和页面其他区块之间要拉开距离,这时候 margin-top、margin-bottom 依然很直接。

3. 需要刻意制造“这个元素自己往外让一点”的效果

比如徽标、浮动按钮、吸顶条这些局部元素,它们的空间关系更偏组件级,而不是容器级。

所以重点不是“全面禁用 margin”。

而是先想清楚:

这个间距,到底属于组件自己,还是属于父级布局?

一个我现在很在意的小原则

如果某个组件离开当前容器之后,间距定义就显得奇怪,那大概率这个间距本来就不该写在组件自己身上。

这句话对我帮助挺大。

因为它会逼着我区分两件事:

  • 组件的样式
  • 布局的样式

以前这两件事我经常混着写,结果就是复用一多,结构就开始打架。

现在我会更倾向于这样处理:

css
.card-list {
  display: grid;
  gap: 20px;
}
 
.card {
  padding: 16px;
  border-radius: 12px;
  background: #fff;
}

容器负责“卡片之间怎么排”。 卡片自己负责“卡片长什么样”。

这样拆开之后,代码会清楚很多。

最后

我现在不是不用 margin 了,而是不会再第一反应就拿它处理一组元素之间的间距。

因为很多时候,我真正想控制的是布局关系。 而不是给某个元素强行塞一个外边距。

对这类问题来说,gap 的语义更准,结构也更稳。

尤其是列表、卡片、标签、按钮组、表单堆叠、响应式布局这些场景,它通常比 margin 更像是“站在布局这一层解决问题”。

如果一句话总结我现在的习惯,就是:

能由容器统一控制的间距,就尽量别让子项自己背着走。