CSS变量非响应式状态,需通过DOM属性(如data-expanded)触发选择器匹配才能生效;直接setProperty修改变量值无效,因无对应CSS规则监听;须配合属性选择器、:root/:host作用域管理及calc()模拟二值状态。

CSS变量本身不能“共享状态”,只能传递静态值;所谓“跨组件状态共享”本质是用变量映射 UI 状态(如 data-theme、data-mode),靠 JS 触发属性变更来驱动样式响应。
为什么直接改 --is-expanded 不生效
很多人写 element.style.setProperty('--is-expanded', 'true') 后发现样式没变,是因为 CSS 变量的计算依赖选择器匹配和级联,不是响应式数据源。变量值变了,但没有对应规则监听它 —— 就像改了 const x = 1,但没地方 if (x === 1) {...}。
- 必须配合属性选择器或伪类使用,例如:
[data-expanded="true"] { --bg: #007bff; } - 不能只靠 JS 改变量,还要同步更新能触发重绘的 DOM 属性(如
dataset.expanded或setAttribute('data-expanded', 'true')) - 避免在内联样式里设变量:
style="--is-expanded: true"是无效的,style无法影响 :root 或父级作用域的变量继承链
:root 与组件局部 :host 的变量作用域差异
在 Shadow DOM 场景下(Web Components / Lit / Stencil),:root 对子组件不可见,必须显式透传或使用 :host + ::slotted。
- 外部主题控制:在 light DOM 写
:root[data-theme="dark"] { --color-text: #e0e0e0; },组件内用color: var(--color-text);即可响应 - 组件封装内控:Lit 中推荐用
:host([expanded]) { --icon-rotate: 180deg; },再通过this.setAttribute('expanded', '')切换 - 别在每个组件里重复声明
--primary—— 它该由顶层:root或:host-context(...)提供,否则主题切换时要改 N 处
用 matchMedia + CSS 变量模拟“响应式状态”
媒体查询本身不是状态,但可以绑定到变量,让组件样式随系统偏好自动切换,无需 JS 监听。
立即学习“前端免费学习笔记(深入)”;
:root {
--prefers-dark: 0;
}
@media (prefers-color-scheme: dark) {
:root {
--prefers-dark: 1;
}
}
.card {
background: calc(var(--prefers-dark) * #121212 + (1 - var(--prefers-dark)) * #ffffff);
}
- 这个技巧适合简单二值状态(深/浅、开/关),不适用于多态(如
loading/error/success) -
calc()里用0/1做开关是目前最兼容的写法,Chrome/Firefox/Safari 均支持,但 Safari 15.4 之前不支持var()在calc()外部参与运算 - 别试图用
transition: --bg 0.2s—— CSS 变量本身不支持过渡,得靠属性(如background)承接后才可动
真正容易被忽略的是:CSS 变量不是 React State,它没有依赖追踪、不会 diff、不触发重渲染。所有“状态感”都来自你手动把 JS 状态映射成 DOM 属性,再让 CSS 去匹配那个属性 —— 这中间少一步,整个链路就断了。


















