现代 CSS 布局:Flexbox、Grid 与 Container Queries
还在用 float 和 margin 居中?现代 CSS 早就不一样了。本文讲清 Flexbox(一维)与 Grid(二维)的分工、居中/等高/圣杯布局的现代写法、Container Queries(组件级响应式)、以及 CSS 的新能力(@layer/:has/逻辑属性),让你用更少的代码写出更强的布局。
本文要点
- 主题聚焦:React、TypeScript
- 适合人群:独立开发者、全栈工程师、AI 应用开发者
- 核心价值:快速理解背景、实践路径、常见误区和工程建议
如果你还在写 display: inline-block 加 vertical-align: middle,或者用 float: left 配合一个 overflow: hidden 的 clearfix —— 这篇是写给你的。
现代 CSS 早就不是十年前那套靠 hack 缝合的样子了。Flexbox、Grid、Container Queries、@layer、:has、逻辑属性……这些不是"新潮玩具",而是今天浏览器 baseline 里就可以直接用的生产力工具。一个明显的体感:很多以前必须借助 JS 库(或者一堆 BEM + SCSS 嵌套)才能搞定的布局,现在纯 CSS 几行就能写完,而且更稳、更可维护。
这篇把日常用得最多的几块讲透:Flexbox、Grid、Container Queries,以及几个改变游戏规则的新特性。
一、Flexbox:一维布局的瑞士军刀
Flexbox 解决的是一维布局问题——一行或者一列。它的心智模型很简单:容器是 flex container,孩子是 flex item,你决定一根"主轴"(row 或 column),剩下的孩子就顺着主轴排。
.toolbar {
display: flex;
flex-direction: row; /* 主轴 */
align-items: center; /* 交叉轴对齐 */
justify-content: space-between;
gap: 12px;
}
几个关键概念记一次就够:
justify-content管主轴方向怎么分布(flex-start / center / space-between / space-evenly)。align-items管交叉轴方向怎么对齐(默认stretch,常改成center或flex-start)。gap是 Flexbox 终于支持的间距属性,不要再用 margin 互推了。gap不会在首尾产生多余间距,改起来一处搞定。flex: 1是flex-grow: 1; flex-shrink: 1; flex-basis: 0%的简写,意思是"剩余空间我全吃"。
典型场景:顶部导航、操作按钮工具栏、卡片头部(图标 + 标题 + 操作)、单行单列的任何线性排布。
.nav {
display: flex;
align-items: center;
gap: 24px;
}
.nav .logo { flex: 0 0 auto; }
.nav .menu { flex: 1; } /* 菜单吃掉中间所有空间 */
.nav .actions { flex: 0 0 auto; }
一个高频误区:flex-grow 只分剩余空间。如果所有 item 本来就撑满了容器,flex: 1 看起来啥也没干。想真正"等分",记得 flex-basis: 0(也就是直接写 flex: 1)。
二、Grid:二维布局的正解
只要布局同时涉及行和列,就轮到 Grid 出场了。Flexbox 在二维场景下会让你套很多层 container,而 Grid 一层就能讲清楚。
.page {
display: grid;
grid-template-columns: 240px 1fr 200px;
grid-template-rows: auto 1fr auto;
grid-template-areas:
"header header header"
"sidebar main aside"
"footer footer footer";
min-height: 100vh;
}
.header { grid-area: header; }
.sidebar { grid-area: sidebar; }
.main { grid-area: main; }
.footer { grid-area: footer; }
区域命名(grid-template-areas)是 Grid 最被低估的能力。看一眼 CSS,整个页面的骨架就浮现出来了——这比一坨 grid-column: 1 / 4 直观太多了,改起来也只需要动 ASCII 图。
另一个杀手锏是 auto-fit + minmax,一行写出"卡片自适应网格":
.cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 16px;
}
这一行的意思是:每列最少 280px,容器变宽就自动塞更多列,剩一点空间就平均分给所有列。没有媒体查询,没有 JS 测宽,卡片网格自动从一列变两列、三列、四列。auto-fit 和 auto-fill 的区别就一句:auto-fit 会把空轨道折叠掉(让现有卡片撑满),auto-fill 会保留空轨道。99% 的卡片场景你要的是 auto-fit。
三、Flexbox vs Grid:别再选边站
社区总有人问"到底用哪个"。答案是看维度:
- 一维(一行或一列内容互相挤压、分配空间)→ Flexbox。
- 二维(同时要规划行和列,比如整个页面骨架、表单的两列表单)→ Grid。
实践中两者经常组合用:外层 Grid 划页面分区,内层 Flexbox 处理组件内的线性排布。这没有任何矛盾。
.card {
display: flex; /* 组件内部:一维 */
flex-direction: column;
gap: 12px;
}
.card-deck {
display: grid; /* 外层:二维卡片网格 */
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 24px;
}
判断标准很简单:心里画一张图,如果只需要数一行/一列就够 → flex;如果需要行列同时考虑 → grid。
四、居中:一行代码的事
"如何垂直居中"曾经是 CSS 面试的经典折磨题。现在?一行:
.center {
display: grid;
place-items: center;
}
place-items 是 align-items 和 justify-items 的简写,Grid 下同时管两个方向。Flexbox 也能两行搞定:
.center-flex {
display: flex;
align-items: center;
justify-content: center;
}
至于绝对定位 + transform: translate(-50%, -50%) 的老写法——还在用,但只在确实需要"脱离文档流、相对任意祖先定位"时才用。日常居中,Grid 一行就够。
五、Container Queries:组件级响应式
这是近几年最重要的 CSS 更新之一。
@media 查的是视口宽度,但你写的组件根本不知道自己被放进了 1200px 的主区还是 320px 的侧边栏。同一个"用户卡片",在桌面主区是横向布局,塞进移动端侧边栏就该变纵向——但视口没变,@media 完全无能为力。于是开发者只能塞一堆 prop 进组件,或者上 ResizeObserver。
Container Queries 让组件根据自己父容器的宽度响应:
.card-host {
container-type: inline-size; /* 声明这是一个容器 */
}
@container (min-width: 480px) {
.user-card {
display: flex;
flex-direction: row;
gap: 16px;
}
}
@container (max-width: 479px) {
.user-card {
display: flex;
flex-direction: column;
gap: 8px;
}
}
这对组件库是革命性的——卡片、表格、面板这些"被到处复用"的组件,第一次能真正做到"我在哪都长得对",而不用消费方传 compact / variant 进来。配合 cqw、cqh 这种容器查询单位,字号、间距还能随容器大小流畅缩放。
心智切换就一句:@media 看视口,@container 看容器。组件内部布局用 container,页面级整体响应用 media。
六、CSS 新能力:三个值得马上用的特性
1. @layer:层叠控制
大型项目里 CSS 顺序一团乱:第三方库、设计系统 reset、业务样式、覆盖补丁全堆在一起,谁后写谁赢。@layer 给你显式的优先级:
@layer reset, framework, components, utilities;
@layer reset {
* { box-sizing: border-box; margin: 0; }
}
@layer framework {
/* 第三方 / 设计系统样式 */
}
@layer components {
.btn { /* ... */ }
}
@layer utilities {
.text-center { text-align: center; }
}
声明顺序决定优先级,后声明的 layer 整体优先级更高。这意味着 utilities 层的一个 .hidden 可以稳定覆盖 components 层的 .btn,而不用 !important 互殴。从此"特异性战争"终于有了和解方案。
2. :has():父选择器
CSS 等了二十年的"根据子元素选父元素":
/* 卡片里有图片时,标题加大 */
.card:has(img) .title { font-size: 1.5rem; }
/* 表单某项出错时,整组高亮 */
.form-group:has(.error) { border-color: red; }
/* 选中的 tab 对应面板 */
.tab:has(*[aria-selected="true"]) { /* ... */ }
以前这些都必须靠 JS 加 class 才能做到。:has() 让一大批"状态联动样式"回归 CSS 本职,组件库和表单场景尤其受益。注意 :has() 内部相对昂贵,但现代浏览器已经做了大量优化,常规用法无需担心性能。
3. 逻辑属性:天然国际化
margin-left 是物理方向,阿拉伯语 / 希伯来语这种 RTL 语言下会整个反过来错位。逻辑属性跟书写方向走:
.badge {
inset-inline-start: 12px; /* 替代 left,RTL 下自动变 right */
padding-inline: 16px; /* 替代 padding-left + right */
margin-block-end: 8px; /* 替代 margin-bottom */
border-inline-start: 1px solid #ddd;
}
inline 是文字流动方向(横排下等于水平),block 是块叠放方向(等于垂直)。start / end 取代 left / right。一旦项目要做国际化,这一组属性能省掉无数 dir="rtl" 下的特殊补丁。新项目从第一天就用逻辑属性,几乎零成本,收益巨大。
小结
- Flexbox 管一维,Grid 管二维,别选边,常组合。判断标准:只数一行/一列用 flex,行列同时规划用 grid。
- 居中是
display: grid; place-items: center;一行的事,把绝对定位 + translate 留给真正需要脱离文档流的场景。 auto-fit + minmax一行写出自适应卡片网格,告别一堆媒体查询。- Container Queries 让组件根据容器而非视口响应,是组件库复用的真正解药;页面级用
@media,组件级用@container。 @layer、:has、逻辑属性解决的是过去只能靠 JS 或!important互殴的硬骨头,新项目该直接用上。
一句金句收尾:现代 CSS 的最大变化不是多了几个属性,而是把过去"靠 JS 与 hack 才能做对的事"还给了 CSS 本身——写更少的代码,得到更强的布局。
Related