CSS变量是唯一规范支持且不破坏Shadow DOM封装的主题通信方式,但需显式挂载在宿主元素上才能被shadow内var()继承获取,否则静默回退fallback。
CSS 变量是唯一被规范允许、无需 polyfill 且不破坏 Shadow DOM 封装性的主题通信通道;但变量不会“自动穿透”,必须显式挂载在宿主元素上,否则 fallback 会静默生效,主题切换完全失效。
为什么
在 shadow 内取不到值
浏览器对
的求值是继承式查找:从当前元素开始,沿 DOM 树向上直到
根节点,逐层检查是否定义了该变量。Shadow boundary 不阻断这个过程,但前提是变量声明必须落在该继承链上——即宿主元素(如
)自身,而非它的父容器或
。
错误写法:
→ shadow 内
回退到 fallback,无警告
正确写法:
或 JS 动态设置:
小程序(如 Vant Weapp)中,
里必须用
,因不支持
与
怎么配合变量控制宿主外观
变量只能影响 shadow 内部样式;要让主题也作用于宿主元素自身(比如按钮整体边框、禁用态透明度),必须靠
选择器读取变量并应用。
必须写在 shadow root 的
里,不能放在外部 CSS 中
示例:
可响应
这类结构,适合全局暗色模式,但 Safari 16–17.3 对嵌套伪类支持不稳定
避免
这类后代选择器——语法非法,会被忽略
哪些变量该暴露?暴露太多或太少分别会怎样
暴露变量 = 暴露组件的视觉 API。粒度太粗(只暴
)导致定制僵硬;粒度太细(暴
)等于开放实现细节,破坏封装性。
必暴露三级变量:
、
、
、
—— 直接决定组件基础视觉风格
慎暴露:
、
—— 若组件内部有复杂排版逻辑,外部覆盖易引发错位
禁止暴露:
、
、任何带下划线前缀(如
)的变量 —— 这些属于实现细节,未来可能删改
所有变量必须带 fallback:
,否则未设时渲染为 inherit 或初始值,UI 崩塌无提示
和
是变量的补位,不是替代
当组件没暴露某个子元素的变量(比如图标颜色、输入框 placeholder 文字),又需要精细控制时,
才是解法。但它依赖组件主动标记
,且兼容性比变量差。
组件内需写:
外部写:
—— 依然建议结合变量使用,保持主题一致性
不支持嵌套:
无效;
无法覆盖 shadow 内已设的内联
兼容性底线:Chrome 95+、Firefox 119+、Safari 17.4+、微信小程序基础库 2.28.0+
真正容易被忽略的是变量作用域的“单向性”:它只解决“外部传入值”,不解决“内部状态反馈”。比如主题切换后,组件无法主动通知外部“我已适配完成”;想做运行时校验(如检测
是否合法 hex),得靠 JS 主动读取
并解析,浏览器不提供变更事件。
var(--theme-color)var()document:rootbody { --theme-color: #1a73e8; }var(--theme-color)ui-button { --theme-color: #1a73e8; }el.style.setProperty('--theme-color', '#1a73e8')app.wxsspage { --theme-color: ... }:root:host:host-context():host:host:host { border: var(--button-border, 1px solid #ddd); opacity: var(--button-opacity, 1); }:host-context(.dark):host .inner--primary-color--icon-margin-left--text-color--bg-color--border-radius--shadow--font-size--line-height--internal-id--z-index-step__privatecolor: var(--text-color, #333);::part()exportparts::part()exportpartsui-button::part(icon) { color: var(--icon-color, #666); }ui-button::part(icon)::before!importantstyle--bg-colorgetComputedStyle(host).getPropertyValue('--bg-color')