CSS Modules
让 CSS 类名和动画名称默认拥有局部作用域的模块化方案。
项目概述
CSS Modules 保留标准 CSS 的编写方式,在构建时把局部名称转换为唯一标识,并向 JavaScript 导出名称映射,从而减少全局样式冲突。
CSS Modules 不是单一运行时库,而是一套由构建工具实现的 CSS 模块化约定:模块文件中的类名和动画名称默认处于局部作用域,编译后变成全局唯一标识;JavaScript 或 TypeScript 导入该文件时会获得“源码名称到生成名称”的映射对象。开发者仍然编写普通 CSS,并可通过 composes 复用其他局部类,通过 :global 显式进入全局作用域。CSS Modules 的底层交换格式称为 ICSS,不同 Bundler 的文件命名、导出方式和生成类名可能略有差异。Vite、Webpack、Next.js 等主流工具通常已经集成该能力,因此它常被用作组件样式隔离的低抽象默认方案。
主要特点
CSS Modules 以文件和局部名称为边界解决传统 CSS 的全局冲突,同时保留选择器、媒体查询、变量和动画等原生能力。
默认局部作用域
模块中的类名和动画名称在构建时转换为唯一标识,不同文件可以安全复用 card、title 或 button 等常见名称。
显式模块依赖
组件通过 import 获得类名映射,样式依赖和组件依赖出现在同一模块图中,便于构建工具追踪与拆包。
继续使用标准 CSS
可直接使用选择器、伪类、媒体查询、Container Query、CSS Variables、Animation 和 Cascade Layer,无需学习 JavaScript 样式对象。
类名组合
composes 可以让一个局部类复用同文件或其他模块中的类名,减少重复声明,并在导出映射中返回组合后的 Class List。
可控的全局逃生口
:global 可用于 Reset、第三方组件状态类或应用级约定,让局部隔离与必要的全局选择器共存。
构建时名称转换
开发环境可保留易读文件名和局部名称,生产环境可生成更短标识;浏览器不需要额外 CSS Modules 运行时。
预处理器兼容
许多工具支持 *.module.scss、*.module.less 等组合形式,可在局部作用域基础上继续使用 Sass、Less 或 PostCSS。
主流工具内建支持
Vite、Webpack、Next.js 以及多种框架工具链都能处理 CSS Modules,常见项目无需自行实现名称转换。
适用场景
适合希望继续编写普通 CSS,又需要组件级隔离、清晰依赖关系和低迁移成本的应用与组件库。
React、Vue 与组件化应用
每个组件维护自己的 *.module.css,通过导入映射应用局部类,降低页面和组件之间的意外覆盖。
传统 CSS 渐进迁移
团队可以逐个文件改为 .module.css,无需一次性重写为 Utility CSS 或 CSS-in-JS。
中小型产品界面
适合营销站、后台、内容应用和 SaaS 产品,以很少的工具概念获得可靠的组件样式隔离。
保留原生 CSS 能力的团队
当团队熟悉 CSS Cascade、Selector、Media Query 和 Variable 时,CSS Modules 能在不改变作者模型的情况下增加局部作用域。
服务端渲染与静态生成
样式由框架构建流程提取或拆分,适合 SSR 和 SSG;具体加载顺序与 CSS Chunk 行为由所用框架决定。
轻量组件库
可为组件提供稳定的局部样式边界,但发布前要明确生成 CSS、导出映射和消费者 Bundler 的处理方式。
优点与注意事项
技术选型不仅要看能力,也要理解它带来的团队成本。
主要优点
CSS Modules 擅长的地方
- 保留普通 CSS 语法,现有 CSS 知识、浏览器工具和大部分 PostCSS 生态可以继续使用
- 类名默认局部化,显著降低大型应用中的命名冲突和意外级联
- 没有浏览器端样式生成引擎,最终运行方式就是普通类名与 CSS
- 组件显式导入样式,依赖关系比全局样式表更容易追踪
- 可按文件渐进采用,适合从传统全局 CSS 迁移
- 主流 Bundler 与框架普遍支持,基础使用通常不需要额外配置
需要注意
采用前应考虑的问题
不同 Bundler 对导出方式、类名格式、ICSS、命名规则和 Source Map 的支持可能不同,跨工具迁移前需要核对文档。
继承、CSS Variables、元素选择器、全局 Reset 和 Cascade Layer 仍可跨越组件边界,团队仍需理解标准 CSS 级联。
TypeScript 工具链常把模块导出视为字符串字典,不一定能发现写错的具体类名;严格类型需要额外声明生成器或编辑器插件。
composes 适合复用类名,但复杂 Size、Tone、State 和 Compound Variant 仍需组件代码、类名工具或更高层方案组织。
大量 :global 会重新引入命名冲突和远距离覆盖,应限制用途并为真正的全局类建立明确命名规则。
运行时颜色、坐标和尺寸通常要通过 CSS Variables、Inline Style 或预定义 Modifier Class 传入,CSS Modules 不会动态生成规则。
多个模块拥有相同特异性时,最终导入与 CSS Chunk 顺序仍会影响结果,不能只依赖源码文件名推断优先级。
如果直接发布未编译的 .module.css,消费者必须具备兼容处理能力;如果预编译,则要避免类名映射、CSS 文件和 JavaScript 导出失配。
快速开始
使用 Vite 自带的 CSS Modules 支持创建 React 示例,无需安装额外的样式运行时或 Vite 插件。
bashnpm create vite@latest css-modules-app -- --template react-ts
cd css-modules-app
npm install
npm run devcss.card {
width: min(32rem, 100%);
padding: 1.5rem;
border: 1px solid rgb(148 163 184 / 0.3);
border-radius: 1rem;
background: var(--surface);
color: var(--text);
box-shadow: 0 20px 60px rgb(15 23 42 / 0.12);
}
.title {
margin: 0 0 0.5rem;
color: var(--brand);
}
.action {
margin-top: 1rem;
padding: 0.75rem 1rem;
border: 0;
border-radius: 0.75rem;
background: var(--brand);
color: white;
cursor: pointer;
}
.action:hover {
opacity: 0.9;
}
@media (min-width: 48rem) {
.card {
padding: 2rem;
}
}tsximport styles from "./Card.module.css";
export function Card() {
return (
<article className={styles.card}>
<h1 className={styles.title}>CSS Modules</h1>
<p>熟悉的 CSS,默认局部的类名。</p>
<button className={styles.action}>开始使用</button>
</article>
);
}css.light {
--brand: #1572b6;
--surface: #ffffff;
--text: #0f172a;
}
.dark {
--brand: #60a5fa;
--surface: #0f172a;
--text: #f8fafc;
}
.page {
min-height: 100vh;
display: grid;
place-items: center;
padding: 1.5rem;
background: var(--surface);
color: var(--text);
}tsximport styles from "./Page.module.css";
import { Card } from "./Card";
export function Page({ dark = false }: { dark?: boolean }) {
const themeClass = dark ? styles.dark : styles.light;
return (
<main className={`${themeClass} ${styles.page}`}>
<Card />
</main>
);
}css.buttonReset {
appearance: none;
border: 0;
font: inherit;
}
.primaryButton {
composes: buttonReset;
padding: 0.75rem 1rem;
border-radius: 0.75rem;
background: #1572b6;
color: white;
}bashnpm run build
npm run preview下一步:默认优先使用局部类,把 :global 限制在 Reset、第三方挂载点或明确的公共约定中。生成类名属于构建细节,不应在测试、脚本或外部系统中硬编码。
类似项目
这些方案都能让组件拥有局部样式,但在作者语言、类型安全、主题系统、原子化和构建配置上采用了不同抽象。
CSS Modules vs vanilla-extract
CSS Modules 和 vanilla-extract 都在构建时生成局部类名,并让组件通过模块导入使用样式。CSS Modules 直接编写 CSS,抽象更低;vanilla-extract 在 .css.ts 中使用类型化对象,并内建主题契约、变量和可选 Recipes、Sprinkles 等设计系统能力。
| 比较维度 | CSS Modules | vanilla-extract |
|---|---|---|
| 作者语言 | 普通 CSS,可结合 Sass、Less 或 PostCSS | .css.ts 文件中的 TypeScript 样式对象 |
| 局部作用域 | 类名和动画名称默认局部化 | style、keyframes 和变量生成局部标识 |
| 类型安全 | 基础导入有类型,精确类名通常需要额外工具 | CSS 属性、值、主题契约和 Recipe Variant 可静态检查 |
| 主题能力 | 通常自行组织 CSS Variables 和主题容器类 | 内建 createThemeContract、createTheme 与 assignVars |
| 组件变体 | 通过多个类、组件条件和类名组合工具实现 | 可选 Recipes 提供类型安全 Variant 与 Compound Variant |
| 动态值 | 使用 CSS Variables、Inline Style 或 Modifier Class | 使用预生成样式或 Dynamic 包更新类型化变量 |
| 构建支持 | 常由框架或 Bundler 原生提供 | 需要安装对应 vanilla-extract 官方集成 |
| 更适合 | 偏爱原生 CSS、渐进迁移和低抽象的团队 | 需要类型安全 Token、主题和组件样式 API 的设计系统 |
如果团队希望继续直接编写 CSS,只解决全局命名冲突并保持最低工具抽象,CSS Modules 通常是稳妥默认选择;如果项目需要 TypeScript 检查 CSS 值、强制多个主题实现同一 Token 契约,或建立 Recipes 与 Sprinkles 这样的设计系统 API,vanilla-extract 更完整。两者都依赖构建处理,组件库场景应重点验证消费者工具链和 CSS 输出策略。
资料核验
版本、维护信息与本页采用的官方资料来源。
CSS Modules 是由不同构建工具实现的模块化约定,没有统一的软件包版本。本页依据 CSS Modules 官方文档仓库、Vite CSS Modules 功能说明和 Webpack css-loader 文档整理;快速开始采用 Vite 当前内建的 *.module.css 工作流。
官方仓库未归档,核验时最近可见的代码活动日期为 2024 年 3 月 23 日。项目更新频率较低但仍有维护迹象,因此标记为稳定维护,而不是活跃更新。