全局变量必须声明在 :root,否则 js 读取、ssr 渲染、伪元素样式都会出问题;所有 var() 调用须带默认值;变量应分三层:基础值、语义令牌、组件命名空间;暗色模式必须用 @media 全量覆盖语义令牌。

全局变量必须声明在 :root,否则 JS 读取、SSR 渲染、伪元素样式都会出问题——这不是建议,是浏览器行为决定的硬性约束。
为什么所有基础变量都得塞进 :root
很多人在组件里写 .button { --padding: 12px; },以为是“局部”,实际它仍会污染全局作用域。浏览器只认 :root 是唯一能覆盖整个文档树的锚点;getComputedStyle(document.documentElement) 只能读到 :root 下定义的值;服务端渲染时,未挂载 DOM 的组件内变量根本不可见。
-
:root是唯一支持 SSR + CSR 一致性的位置 - 伪元素(如
::before)无法继承组件级变量,除非父元素显式透传 - JS 动态设置
document.documentElement.style.setProperty()只对:root生效 - 构建工具(如 Vite/esbuild)压缩 CSS 时,只保留
:root中被引用的变量,其他一概删除
var() 不带默认值等于主动放弃容错能力
写 color: var(--text-primary) 看似简洁,但一旦变量未定义(比如主题脚本加载失败、CSS 异步加载顺序错乱),该属性直接退化为 unset,文字可能消失或布局塌陷。
- 所有
var()调用必须带默认值:color: var(--text-primary, #333) - 在
calc()里用var()也得设默认值:padding: calc(var(--space-md, 12px) * 2),否则整个表达式失效 - 默认值只能是合法 CSS 值,不能嵌套另一个
var(),例如var(--space-md, var(--space-sm))是非法的
三层变量结构:基础值 → 语义令牌 → 组件命名空间
把所有颜色、间距、圆角都堆进 :root 是失控的开始。真正可控的体系必须分层,且每层职责分明。
-
基础值层(仅
:root):纯数值,无业务含义,例如--size-unit: 4px、--color-black: #000 -
语义令牌层(也只在
:root):用基础值推导,供组件直接引用,例如--space-md: calc(var(--size-unit) * 3)、--color-bg-surface: var(--color-neutral-50) -
组件专属层(在组件选择器内):带命名空间,仅限内部使用,例如
.card { --card-padding: var(--space-md); },禁止出现在:root中
暗色模式必须用 @media (prefers-color-scheme: dark) 全量重置
只靠 JS 设置 document.documentElement.setAttribute('data-theme', 'dark') 再配 CSS 规则,会导致 macOS 自动切换失效、color-scheme: dark 不触发、系统级字体抗锯齿异常等问题。
- 必须用媒体查询直接覆盖整套语义令牌:
@media (prefers-color-scheme: dark) { :root { --color-bg-surface: #121212; --color-text-primary: #e0e0e0; } } - 不要在
:root里写--is-dark: 0再靠calc()模拟逻辑,那会绕过系统检测,且无法响应用户手动切换 - 媒体查询必须覆盖所有语义令牌,漏一个就可能造成某块区域白屏或对比度崩溃
最常被忽略的是变量与 JS 的联动时机:getComputedStyle(document.documentElement).getPropertyValue('--color-primary') 在样式表尚未解析完成时读不到动态设置的值,必须等 DOMContentLoaded 或监听 style 标签的 load 事件。这点不处理好,主题切换就会有闪屏或错位。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











