必须将所有主题变量声明在 :root 中,因其是 CSS 唯一全局锚点,具备强制继承性、更高特异性(0,1,0)及跨 Shadow DOM/SSR/重置样式等场景的稳定性;html 或 body 声明语义弱、兼容差、传播不可靠。

必须把所有主题变量写在 :root 里,否则切换必然失效——这不是建议,是 CSS 自定义属性作用域的硬性限制。
为什么只能用 :root,不能用 html 或 body
:root 是 CSS 规范唯一指定的“全局锚点”,它强制继承、特异性更高(0,1,0),且在 Shadow DOM、SSR、all: unset 元素等场景下仍能稳定传播变量。而 html { --x: red; } 虽然语法可行,但语义弱、兼容边界模糊;body { --x: red; } 更危险—— 自身、 内样式、未包裹的 <input> 或 <button></button> 都拿不到变量,直接 fallback 到透明或浏览器默认值。
:root 里变量怎么声明才不静默失效
常见错误不是写错语法,而是漏声明、拼错名、或值非法:
- 所有主题下会用到的变量,必须在默认
:root块中完整声明,哪怕深色模式下值相同也不能省——例如--shadow-sm在:root[data-theme="dark"]里写了,但默认:root没定义,页面一渲染就 fallback 成transparent - 变量名必须全小写、用短横线分隔:
--font-size-lg合法,--font size lg或--FontSizeLg都无效 - 值要带单位(
--spacing-md: 16px),不能写--size: 1rem * 2(CSS 不支持运算),要用calc(var(--unit) * 2) - 声明末尾必须加分号,漏掉一个分号会导致后续所有变量静默失效
主题切换必须用 data-theme 属性,别信 @media (prefers-color-scheme)
单纯靠媒体查询写 @media (prefers-color-scheme: dark) { :root { --bg: #1a1a1a; } } 有两大实际问题:首次渲染可能取错值(系统通知晚于页面加载),且和用户手动点击“切换主题”按钮冲突,导致样式逻辑混乱难调试。
立即学习“前端免费学习笔记(深入)”;
正确做法是收口到 data-theme:
- 定义多个
:root[data-theme="dark"]、:root[data-theme="blue"]块,只覆盖需要变的变量 - JS 切换时用
document.documentElement.setAttribute('data-theme', 'dark'),不要操作className,避免清掉其他 class(比如 SSR 注入的或 A/B 测试用的) - 所有
var()必须带 fallback,例如background-color: var(--color-surface, #ffffff),否则变量未定义时样式直接崩
哪些值适合放 :root,哪些绝对不该放
适合放的:语义化颜色(--color-primary)、基础间距单位(--spacing-xs)、字体基准(--font-size-base)、圆角/阴影 token(--radius-sm、--shadow-md)——这些是设计系统的核心 token,改一处,全站响应。
不该放的:
- 组件级样式值(如
--button-padding),应由组件自身选择器定义 - 需 JS 实时计算的动态值(比如视口宽度百分比),CSS 变量不响应 resize,得用
style.setProperty() - 带单位的复合表达式(如
--size: 1rem * 2),CSS 不支持运算,必须提前算好或用calc()
最常被忽略的一点:变量修改必须走 JS 的 style.setProperty(),改完 CSS 文件不会热更新——document.documentElement.style.setProperty('--primary', '#007bff') 才是唯一生效路径。


















