composes必须写在类选择器大括号内,不能独立声明;它仅复制普通属性,不继承伪类、媒体查询和css变量,且from路径须为相对.module.css文件。

composes语法必须写在局部类名定义内部
很多人写成 .btn { composes: base; } 却发现没生效,根本原因是 composes 不是独立声明,它只能作为某个类的属性出现在其 CSS 块中。也就是说,你不能把它当“import”用,也不能放在规则外或注释后。
正确写法是:
.btn {
composes: base from './base.module.css';
background: blue;
}
-
composes必须紧跟在类选择器大括号内,且前面不能有空行或注释 - 多个复用可写在同一行:
composes: base, hoverable from './base.module.css'; - 不支持跨文件链式复用(比如 A composes B,B composes C,A 无法直接继承 C 的样式,除非显式再写一次)
from路径必须是相对路径,且文件需带.module.css后缀
Webpack 和 Vite 的 css-loader 默认只对 .module.css(或 .module.scss 等)启用 Modules 模式。如果你从 base.css 导入,composes 会静默失败——不会报错,但样式不生效。
常见错误示例:
.btn {
composes: base from './base.css'; /* ❌ base.css 不启用 Modules,base 不会被解析 */
}
- 务必使用
./base.module.css这类命名,否则from后路径找不到对应模块导出 - 路径是相对于当前 CSS 文件的,不是 JS 文件;
../和./都有效,但不能用绝对路径或别名(如@/styles/base),除非你额外配了css-loader的alias - Vite 中若用
resolve.alias,需同步配置css.modules.localsConvention,否则from仍可能解析失败
composes不继承伪类、媒体查询和自定义属性
composes 只复制声明块中的普通属性(如 color、padding),它不会把 :hover、@media 或 --primary-color 带过去。这是最容易被忽略的限制。
比如你写了:
/* base.module.css */
.base {
color: red;
}
.base:hover {
color: orange;
}
然后在另一个文件里:
.btn {
composes: base from './base.module.css';
font-weight: bold;
}
结果是:.btn 有 color: red 和 font-weight: bold,但 :hover 样式完全丢失。
- 需要伪类行为,得在目标类里重写:
.btn:hover { composes: base:hover from './base.module.css'; }(注意:这种写法仅在 Webpack 5+ / css-loader 6+ 支持,旧版本不识别:hover在composes中) - 自定义属性(CSS 变量)要手动透传,
composes不处理var(--x)的引用关系 - 媒体查询必须单独包裹,
composes无法导入@media (min-width: 768px) { .base { ... } }内部内容
开发时类名拼接顺序影响样式优先级
当你用 composes 复用多个类,比如 composes: a b from './x.module.css';,最终生成的 HTML class 属性值是按顺序拼接的:class="a-hash b-hash current-hash"。但 CSS 优先级只看选择器权重,不看 class 出现顺序。
真正影响表现的是:如果 a 和 b 定义了同一个属性(如都设了 margin),后写的那个会覆盖前一个——因为它们最终编译为独立的 CSS 规则,而规则顺序由文件引入和 composes 声明位置共同决定。
- 不要依赖 “先
composes哪个就优先” —— 实际以构建后 CSS 文件中规则出现的先后为准 - 调试时打开浏览器开发者工具,看 computed styles 里哪条规则被划掉,就能反推冲突来源
- 想强制覆盖,最稳妥方式是在当前类里显式重写,而不是靠
composes顺序
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











