如何在现代 Web 项目中组织 CSS
CSS 易于编写但难以维护。几百行代码感觉还能掌控,但六个月后你会害怕改动任何东西,因为不知道会破坏什么。问题不在于 CSS 本身——而在于缺乏结构。
以下是一种实用的 CSS 组织方法,它具有可扩展性、可读性,并且能与现代工具链良好配合。
核心要点
- 使用原生 CSS 级联层(
@layer)来控制样式顺序,消除特异性(specificity)冲突。 - 采用从基础到语义的层级结构组织设计令牌(design tokens),使主题切换只需在一处修改,而非数十处。
- 使用 CSS Modules 或类似的作用域工具将组件样式与组件放在一起。
- 保持嵌套层级浅显——通常两层深度是极限,超过后特异性问题会重新出现。
- 采用清晰的文件结构,将全局样式与组件样式分离,避免意外的样式泄漏。
从清晰的层级结构开始
CSS 架构中最重要的决策是控制样式存放位置以及它们应用的顺序。原生 CSS 级联层(@layer)让这一点变得明确。
@layer reset, tokens, base, components, utilities;
预先声明层级意味着后面的层会覆盖前面的层——无论特异性如何。不再需要用越来越具体的选择器或 !important 技巧来对抗级联。
一个实用的层级顺序:
- reset — 标准化浏览器默认样式
- tokens — CSS 自定义属性(你的设计令牌)
- base — 元素级样式(
body、h1、a) - components — 作用域内的 UI 样式
- utilities — 单一用途的覆盖样式
这种结构为你提供了可预测的特异性和清晰的心智模型,让你知道任何给定样式应该放在哪里。
设计令牌属于基础层
设计令牌是颜色、间距、排版和其他设计决策的命名值。定义为 CSS 自定义属性后,它们为整个代码库创建了单一的真实来源。
:root {
--color-primary: oklch(55% 0.2 250);
--space-md: 1rem;
--font-body: "Inter", sans-serif;
}
从基础到语义组织令牌:
- 基础层:
--blue-500: oklch(55% 0.2 250) - 语义层:
--color-action: var(--blue-500) - 组件层:
--btn-bg: var(--color-action)
这种层级结构意味着你可以通过更改语义令牌来重新设计整个项目主题,而不是在组件样式中到处查找。
基于组件的样式:限定样式作用域
全局样式表处理基础样式。组件处理其他所有内容。关键原则:将样式与其所属组件放在一起。
CSS Modules 是在 React、Vue 或任何基于打包工具的项目中实现这一点的最直接方式。每个 .module.css 文件默认都是局部作用域——类名在构建时转换为唯一标识符,因此一个组件中的 .button 永远不会与另一个组件中的 .button 冲突。
/* Button.module.css */
.button {
background: var(--btn-bg);
padding: var(--space-sm) var(--space-md);
}
原生 CSS 嵌套(现在所有现代浏览器都支持)也减少了在组件样式中对预处理器的需求:
.card {
padding: var(--space-md);
& .card-title {
font-size: 1.25rem;
}
}
保持嵌套浅显——两层通常就足够了。深层嵌套会重新引发你试图避免的特异性问题。
原生 @scope 现在已广泛可用,但在应用代码中,它仍然不如 CSS Modules 或框架级作用域常见,因此将其视为新兴选项而非默认选择。
Discover how at OpenReplay.com.
实用优先的 CSS:Tailwind v4 的定位
Tailwind CSS v4 采用了不同的方法:不是编写组件 CSS,而是直接在标记中使用实用类组合样式。版本 4 转向了 CSS 优先的配置模型——你在 CSS 文件中使用 @theme 配置 Tailwind,而不是 JavaScript 配置文件。
@import "tailwindcss";
@theme {
--color-primary: oklch(55% 0.2 250);
}
Tailwind 适合希望快速迭代和一致设计约束的团队。权衡之处在于冗长的标记和较少的语义化类名。许多团队使用混合方法:Tailwind 实用类用于布局和间距,CSS Modules 或自定义属性用于复杂的组件逻辑。
实用的文件结构
对于大多数项目,这种结构扩展性良好:
styles/
index.css ← 仅导入,声明 @layer 顺序
tokens.css ← 设计令牌
reset.css ← 浏览器标准化
base.css ← 元素样式
utilities.css ← 辅助类
components/
Button/
Button.jsx
Button.module.css
全局样式放在 styles/ 中。组件样式放在组件旁边。除非有充分理由,否则不会跨越这个边界。
总结
良好的 CSS 组织归结为几个一致的习惯:尽早声明层级顺序,在根层级定义设计令牌,在本地限定组件样式作用域,并保持选择器浅显。你不需要严格的方法论——你需要整个团队都理解和遵循的清晰约定。
从简单开始。只在项目需要时才添加结构。
常见问题
可以。Tailwind v4 是围绕原生级联层(theme、base、components、utilities)构建的。你不需要包装其输出,而是通过将自己的 CSS 放置在适当的层中来控制级联,使其与 Tailwind 的实用类可预测地组合。
它们解决不同的问题。CSS 嵌套减少了冗余并将相关规则分组,但它不会限定类名作用域。CSS Modules 通过在构建时生成唯一标识符来保证局部作用域。对于多个组件可能共享 button 或 title 等类名的项目,CSS Modules 仍然是更可靠的隔离机制。
每个设计令牌都是 CSS 自定义属性,但并非每个自定义属性都是设计令牌。设计令牌代表深思熟虑的设计决策,如品牌颜色、间距比例和字体大小。它们按照从基础到语义的层级组织,因此更改单个语义令牌就可以重新设计整个项目主题,而无需编辑单个组件样式。
像 Tailwind 这样的实用优先 CSS 最适合快速原型开发和偏好在标记中共置样式决策的团队。组件作用域 CSS 适合具有复杂 UI 逻辑或严格关注点分离的项目。许多团队将两者结合使用,使用实用类处理布局和间距,同时为有状态或高度定制的组件保留作用域样式表。
Truly understand users experience
See every user interaction, feel every frustration and track all hesitations with OpenReplay — the open-source digital experience platform. It can be self-hosted in minutes, giving you complete control over your customer data. . Check our GitHub repo and join the thousands of developers in our community..