引言:CSS 写得越久,越容易"越写越乱"
很多团队的 CSS 一开始都很干净:一个 index.css、几个组件样式,看着挺顺。等业务长大、页面变多、组件复用起来,问题就密集出现:
- 改一个按钮样式,其他页面跟着"串味";
- 类名越来越长、选择器越来越深;
- 新人不敢删旧样式,只能不断追加;
- 主题、暗黑模式、品牌换肤变成大型重构。
高效简练的 CSS 架构要解决的核心问题是:如何在规模增长时仍保持可预测、可复用、可删除、可迭代。下面给你一套偏工程化、可落地的做法。
一、问题/背景:为什么 CSS 容易失控?
1)CSS 天生"全局 + 级联",局部改动可能产生全局影响
哪怕只写了:
css
button { border-radius: 6px; }
也可能让第三方组件库、运营页、后台表单全部变样。
2)缺少边界:组件、页面、工具样式混在一起
常见混乱点:
- "布局类"跟"组件类"混用(
.flex、.card、.title同处一室) - 页面私有样式写成公共类,后续被误用
!important变成"权力工具",谁写得狠谁生效
3)缺少统一规则:命名、层级、优先级策略不一致
- 有人用 BEM,有人用驼峰,有人随手
.box1 - 有人习惯 3 层嵌套,有人写 10 层
- 有人全靠覆盖,有人全靠新增
二、解决方案与实现:一套"少规则但强约束"的 CSS 架构
下面这套方案目标是:减少选择器复杂度、控制样式作用域、把"变化"收敛到 token 和组件层。
核心原则(先立规矩)
- 优先作用域隔离:组件样式不污染全局(CSS Modules/Scoped/Shadow DOM 任一都行)
- 禁止深层选择器:选择器深度控制在 1~2 层(最多 3 层,且必须有理由)
- 禁止"按标签写业务样式" :除 reset/typography 外不写
div {}button {}这种全局规则 - 分层管理:token(变量)/ base(基础)/ components(组件)/ utilities(工具)/ pages(页面私有)分开
方案 1:用 CSS Layer(或约定的目录层级)控制"谁覆盖谁"
如果你能用现代浏览器,强烈建议用 @layer 把"层级权力"写死,减少互相覆盖的混战。
css
@layer reset, base, tokens, components, utilities, overrides;
@layer reset {
*, *::before, *::after { box-sizing: border-box; }
}
@layer tokens {
:root{
--space-2: 8px;
--radius-m: 10px;
--color-text: #111827;
--color-surface: #ffffff;
--color-brand: #1677ff;
}
}
@layer base {
body { margin: 0; color: var(--color-text); background: var(--color-surface); }
}
@layer components {
.btn { padding: var(--space-2) 12px; border-radius: var(--radius-m); }
.btn--primary { background: var(--color-brand); color: #fff; }
}
@layer utilities {
.u-mt-2 { margin-top: var(--space-2); }
}
关键收益:
- 不需要靠"更具体的选择器"来赢;
- 工具类层永远不会意外压过组件(或反过来),层级关系清清楚楚。
如果你暂时不能用
@layer,也至少用目录结构实现类似分层,并约定"导入顺序固定"。
方案 2:用 Design Tokens(CSS 变量)把"变化"从组件里抽出来
简练的 CSS 架构不是写更少的 CSS,而是把"经常变的部分"集中管理。
建议至少拆三类 token:
- 颜色:
--color-* - 间距/尺寸:
--space-*、--radius-* - 字体排版:
--font-*
示例:
css
:root{
--space-1: 4px;
--space-2: 8px;
--space-3: 12px;
--radius-s: 6px;
--radius-m: 10px;
--color-text: #111827;
--color-muted: #6b7280;
--color-border: color-mix(in srgb, var(--color-text) 12%, white);
--color-brand: #1677ff;
}
[data-theme="dark"]{
--color-text: #e5e7eb;
--color-muted: #9ca3af;
--color-border: color-mix(in srgb, var(--color-text) 18%, #111827);
--color-brand: #3b82f6;
}
组件里尽量不出现"具体色值",只引用 token:
css
.card{
border: 1px solid var(--color-border);
border-radius: var(--radius-m);
padding: var(--space-3);
}
收益:换主题、改品牌色、统一圆角,不需要"全项目搜索替换"。
方案 3:组件化命名与边界:BEM / CUBE CSS / CSS Modules 三选一(别混用)
你不需要"最潮",需要"统一"。给三种可落地选项:
选项 A:CSS Modules(React/Vue/构建链常见)
特点:天然隔离作用域,最省心。
写法:
css
/* Button.module.css */
.root { padding: var(--space-2) 12px; border-radius: var(--radius-s); }
.primary { background: var(--color-brand); color: #fff; }
JSX:
ini
<button className={`${styles.root} ${styles.primary}`}>OK</button>
建议:中大型工程优先;能有效避免"全局串味"。
选项 B:BEM(适合无构建、或纯 CSS 管理)
lua
.btn { ... }
.btn--primary { ... }
.btn__icon { ... }
建议 :BEM 不要配合深嵌套:.page .btn .btn__icon 这种尽量避免。
选项 C:CUBE CSS(更偏"组合式")
- Composition(组合)
- Utility(工具)
- Block(块)
- Exception(例外)
强调用少量规则组合出 UI,减少写复杂组件样式的冲动。
建议:如果团队习惯 utility-first(如 Tailwind),CUBE 会很顺滑。
方案 4:把"布局"从"组件皮肤"里拆出去(减少耦合)
简练 CSS 的一个秘诀是:组件负责自己长什么样,不负责自己放在哪。
反例(组件自带外边距,导致在不同容器里不好用):
css
.card { margin: 16px; }
推荐(布局交给容器或 utility):
css
.card { padding: var(--space-3); border-radius: var(--radius-m); }
/* 容器布局 */
.list { display: grid; gap: var(--space-3); }
或者用工具类:
css
.u-mt-3 { margin-top: var(--space-3); }
收益:组件可复用性大幅提高,页面样式也更容易删。
方案 5:控制选择器复杂度:两条硬规则让 CSS 立刻变干净
1)不写 ID 选择器 (除非锚点/脚本需要)
2)不写超过 3 层的选择器,并把嵌套当作"坏味道"
坏例子:
css
.page .content .card .header .title span { ... }
好例子(给元素一个明确类名,降低耦合):
.cardTitle { ... }
配合 Stylelint 可以强制执行(团队必备):
- 限制选择器深度
- 禁止
!important - 限制特定模式(如禁止标签选择器用于业务)
三、优缺点分析与实战建议
优点
- 维护成本低:token 集中、组件隔离、层级可控,改动可预测
- CSS 更少:因为不再靠覆盖叠加"打补丁",重复规则会明显下降
- 协作更顺:新人知道该把样式放哪、怎么命名、怎么覆盖
缺点/代价
- 初期要定规范:需要一次性把分层、命名、lint 规则定下来
- 迁移成本:老项目从"全局 CSS + 覆盖"迁到组件化/分层,需要分阶段推进
- 对工具链有依赖(如果选 CSS Modules / PostCSS / Stylelint):需要 CI 与本地开发一致
落地建议(按优先级)
- 先做分层与 token:哪怕不改写法,也先把颜色/间距/圆角集中成变量
- 再做作用域隔离:新组件全部用 CSS Modules/Scoped,旧代码逐步收敛
- 加 Stylelint 约束:没有约束,再好的架构也会回到"谁都能乱写"
- 页面样式页面化:页面私有写在 pages 层,别污染组件层
- 建立"组件状态"标准:hover/active/disabled/focus-visible 写法统一,减少重复
结论:高效简练的 CSS 架构,本质是"把不确定性收口"
CSS 之所以难管,不是因为它弱,而是它太"自由"。真正高效的 CSS 架构会用少量但明确的规则,把自由收束在可控范围:
- 变化交给 token;
- 复杂度止步于组件边界;
- 覆盖关系由 layer/导入顺序决定;
- 可读性和可删除性优先于"暂时能跑"。
随着 @layer、现代颜色/变量能力、组件化框架与 lint 工具成熟,CSS 架构会越来越像一套"前端样式工程体系",而不是零散的样式片段堆叠。
参考资料(可选)
- MDN:@layer(CSS Cascade Layers)developer.mozilla.org/en-US/docs/...
- CUBE CSS:cube.fyi/
- BEM 命名:getbem.com/
- Stylelint:stylelint.io/
- CSS Modules(概念与生态):github.com/css-modules...