CSS变量是Shadow DOM中唯一安全的主题穿透机制,须声明在宿主元素上并配fallback值,动态切换推荐style.setProperty(),复杂定制可结合::part()。

CSS 变量是 Shadow DOM 中唯一安全、标准、可维护的主题穿透机制 —— 其他方式要么已废弃(如 /deep/),要么破坏封装(如直接操作 shadowRoot),要么不跨边界(如普通类名或全局 :root 声明)。
变量必须声明在宿主元素上,不能只靠 :root 或父容器
很多人写 body { --primary-color: #1890ff; } 或 .wrapper { --primary-color: #1890ff; },结果组件内部 var(--primary-color) 读不到。这是因为 Shadow DOM 边界会截断继承链,变量必须显式挂载到宿主自身。
- ✅ 正确:给组件标签直接设属性,
<van-button style="--button-bg: #52c418;"></van-button>或<my-card theme="dark" class="dark-mode"></my-card>配合.dark-mode { --card-bg: #1d1d1d; } - ❌ 错误:把变量写在
app.css里但没加page或组件类前缀(小程序中尤其常见);或写在<div>包裹层上,却忘了该div并非宿主元素 - ⚠️ 注意:
all: initial或all: unset会清空继承链,导致变量丢失 —— 组件内部:host上慎用这类重置
fallback 是必选项,不是可选语法糖
组件内部写 color: var(--text-color); 是危险的。一旦外部没提供该变量,计算值就是 invalid,样式直接回退到浏览器默认(比如黑色文字在深色背景上不可见)。
- ✅ 必须带 fallback:
color: var(--text-color, #333);、border-radius: var(--radius, 4px); - ✅ 复杂值也可 fallback:
box-shadow: var(--elevation, 0 2px 8px rgba(0, 0, 0, 0.15)); - ⚠️ fallback 不支持表达式或嵌套变量,
var(--color, var(--fallback))无效
动态主题切换要走 element.style.setProperty(),别碰 className
企业级主题常需运行时切换(如用户点“深色模式”按钮)。靠切换宿主 class 再用 CSS 规则定义变量,虽可行但耦合重、难调试;直接改 style 属性更可控。
- ✅ 推荐:
buttonEl.style.setProperty('--button-bg', '#13c2c2');—— 仅影响当前实例,不影响其他同类型组件 - ✅ 批量更新:
document.documentElement.style.setProperty('--theme-accent', '#faad14');配合 :host-context()(已废弃)不可靠,应避免 - ⚠️ 小程序中动态绑定 style 要拼完整字符串:
style="{{ 'color: ' + textColor + '; --icon-size: ' + iconSize + 'px;' }}",漏分号或单位会静默失败
复杂定制需求请补位 ::part(),但别绕过变量设计
当组件暴露了 exportparts="icon label",你可以写 my-button::part(icon) { color: var(--icon-color); } 来控制子节点。但它不是替代变量的方案,而是变量体系的延伸。
- ✅ 合理组合:
::part(icon)控制作用域,var(--icon-color)提供主题来源,保持统一治理 - ❌ 禁止滥用:
my-button::part(icon)::before无效;!important无法覆盖 shadow 内部已设的内联style - ⚠️ 兼容性注意:微信小程序需基础库 ≥ 2.28.0;Safari 17.4+、Chrome 95+、Firefox 119+ 支持;旧版 Safari 在
@keyframes中读变量有 bug
真正难的不是写对一句 var(--x),而是整个主题变量体系的命名收敛、fallback 覆盖率、宿主作用域的精准控制 —— 这些地方一松动,隔离就形同虚设,定制就变成 patch 大赛。

















