自定义属性(--)是全局的,同名变量会相互覆盖,导致样式不可预期;必须人工添加模块前缀如--button-color-primary,并配合lint检查,CSS Modules无法自动隔离--。

自定义属性(--* )本身不参与选择器匹配,但它们是全局作用域的——同一名称在不同模块中被反复赋值时,后生效的会覆盖先定义的,导致样式行为不可预期。这不是“类名冲突”,而是“变量劫持”。
为什么 --color-primary 在多个 .module.css 里声明会出问题
CSS 自定义属性遵循 CSS 层叠规则:同名变量按声明顺序和作用域生效。即使用了 CSS Modules,--color-primary 仍属于全局 CSSOM 变量空间,模块之间无法天然隔离。
- 模块 A 的
Button.module.css写了:root { --color-primary: #007bff; } - 模块 B 的
Card.module.css也写了:root { --color-primary: #28a745; } - 最终生效的是后者(取决于加载/插入顺序),Button 组件的
color: var(--color-primary)就悄悄变绿了
给自定义属性加模块前缀是唯一可靠做法
不能依赖构建工具自动哈希自定义属性名(css-loader 的 localIdentName 不处理 --*),必须人工约定命名空间。
- 按 BEM 块名或组件名前缀:如
--button-color-primary、--card-bg-default - 避免泛化前缀(如
--ui-或--global-),它们迟早会在跨团队协作中撞车 - 在 TypeScript 中配合类型安全:为每个模块声明专属的
declare module '*.module.css'类型,只导出带前缀的变量名,防止误用
:host 或 :scope 无法限制自定义属性作用域
有人尝试用 :host { --x: y } 或 :scope { --x: y } 想局部化变量,但无效——CSS 自定义属性没有“作用域链”,:host 只影响选择器匹配,不改变变量可见性。
立即学习“前端免费学习笔记(深入)”;
-
:host是 Shadow DOM 特性,普通 CSS Modules 不生成 Shadow Root -
:scope仅影响后代选择器范围,对var(--x)查找无任何约束 - 真正起效的只有:显式前缀 + 构建时 lint 检查(如用
stylelint规则禁止未加前缀的--*)
最易被忽略的一点:CSS Modules 能哈希类名,但对 --* 完全透明。只要两个模块都写了 --size-md,就必然存在覆盖风险——哪怕它们压根没 import 彼此,只要都在同一个 document 上运行,变量就共享。别指望模块系统替你管变量。


















