CSS自定义属性无文件级作用域,全局污染源于绑定到:root;真正局部化需绑定到组件根选择器,依赖DOM继承而非文件或构建工具。

CSS自定义属性(--x)本身没有文件级或模块级作用域,写在 Button.module.css 或 Component.razor.css 里,--color 仍是全局可读的——除非你主动把它绑到组件根元素上。
为什么 :root { --x: red } 一定会污染全局
:root 是整个文档的根,和文件路径、模块名、编译工具完全无关。Webpack 的 css-loader、Vite 的 CSS 处理器,都不会重写或包裹 :root 块里的 --x 声明。浏览器解析时直接挂到 document.documentElement 上,所有组件都能 var(--x) 读到它。
常见错误现象:
- 改了
Header.module.css里的:root { --text-size: 14px },结果Footer和第三方Modal的文字全变小了 - Blazor 编译后生成的
MyApp.styles.css中,--size出现在顶层规则,没被[b-abc123]包裹
正确做法是:删掉所有 :root 下的可变 --*,只留真正全局常量(如 --color-black: #000)。
立即学习“前端免费学习笔记(深入)”;
如何把 --x 真正限定在组件内
关键不是“在哪写”,而是“声明绑定到哪个选择器”。只要变量在组件根元素上声明,子元素靠继承自然读取,不会逃逸。
- React + CSS Modules:在组件 JSX 根节点加
className="button-root",然后写.button-root { --bg: #007bff; } - Vue:
<div class="card">+.card { --shadow: 0 2px 4px rgba(0,0,0,0.1); },子元素用box-shadow: var(--shadow) - Blazor:
<div class="counter">(必须是根元素),然后在Counter.razor.css写.counter { --count-color: blue; },编译后自动变成div[b-xyz] { --count-color: blue; } - Svelte:推荐在顶层
<div class="svelte-root" style="--icon-size: 20px">,再让内部用font-size: var(--icon-size)
注意:不要用 all: unset 或 all: revert 重置父级变量——这会切断继承链,子组件 var(--x) 返回 unset。
跨子组件传递变量时怎么避免泄漏
父组件要向子组件传主题色,不能靠全局 :root 注入,而应显式承接 + 继承:
- 父组件容器设
style="--button-bg: #28a745",子组件写.btn { background: var(--button-bg, inherit); } - 需要穿透 Shadow DOM 或 scoped 边界时,用
::deep .child { color: var(--text-color); },但前提是父容器已声明该变量并处于继承链上 - 慎用
@import共享变量文件——如果里面含--primary: red这类可变值,导入即全局生效;只放不可覆盖的常量,比如--z-index-overlay: 1000
编译后务必打开生成的 .styles.css 文件,搜索你的 --x,确认它是否出现在带 [b-]、.svelte- 或哈希类的选择器块内。不在,就说明隔离失败。
最易被忽略的一点:自定义属性的“局部性”完全依赖 DOM 继承链和选择器绑定位置,和文件名、构建工具、scoped 关键字都无直接关系——写错选择器,再多配置也白搭。


















