约 7 分钟读完 · 更新于 2026-10-21 · 文中每条都能在下面的配套工具里动手验证
变量在作用域内定义、沿 DOM 继承、可被后代覆盖。这与预处理器变量最大的不同:CSS 变量是运行时值,可以随 DOM 结构、媒体查询、甚至 JS 动态变化。记住读取语法 var(--name, fallback),fallback 只在变量未定义时生效。
:root {
--brand: #10b981;
--space: 8px;
}
.btn {
background: var(--brand);
padding: calc(var(--space) * 2) calc(var(--space) * 3);
color: var(--brand-text, white); /* --brand-text 未定义时用 white */
}把颜色抽到 :root,再为 data-theme="dark" 重定义同名变量,切换主题只需改 html 上的一个属性,零 JS 重绘逻辑、零样式重复。prefers-color-scheme 也可与 data-theme 叠加以支持「跟随系统 + 手动覆盖」三态。
:root {
--bg: #ffffff;
--text: #1a1a1a;
--card: #f5f5f5;
}
[data-theme="dark"] {
--bg: #0f172a;
--text: #e2e8f0;
--card: #1e293b;
}
body {
background: var(--bg);
color: var(--text);
}
/* 切换:document.documentElement.dataset.theme = 'dark' */变量与 calc 的组合能从两三个基础值推导整套刻度,改基础值即全局生效。这是设计令牌的最小实现,比引入整套方案更轻。注意 calc 里变量与数字运算时需要带单位的部分乘数字、数字乘带单位部分,顺序无影响但不能除以带单位值。
:root {
--base: 4px;
--gap-sm: calc(var(--base) * 2); /* 8px */
--gap-md: calc(var(--base) * 4); /* 16px */
--gap-lg: calc(var(--base) * 8); /* 32px */
}
.grid { gap: var(--gap-md); }
.section { margin-block: var(--gap-lg); }变量的「怪行为」大多源于继承模型,记住这张表能避开 90% 的意外:
| 规则 | 行为 | 实战含义 |
|---|---|---|
| 子元素读不到时回退到祖先 | 变量沿 DOM 向下继承 | 在组件根上覆盖变量即可定制组件 |
| var() 的 fallback 只在未定义时生效 | 变量已定义但值非法不会触发 fallback | 非法值会导致属性回退到 initial/inherit,不是 fallback |
| 变量名大小写敏感 | --Color 与 --color 是两个变量 | 统一小写 kebab-case 命名 |
| 变量在元素自身作用域内即时解析 | 在后代重定义立即生效于该后代子树 | 主题可精确到单个组件甚至单个卡片 |
| 循环引用会使属性失效 | a 引 b、b 引 a 时两者均无效 | 令牌分层:基础层不引用派生层 |
普通变量是字符串,无法参与过渡动画。用 @property 注册后变量获得类型、初始值与继承语义,background 渐变角度、颜色等都能平滑动画。兼容性:Chrome/Edge/Safari 已支持,Firefox 近期版本也已落地,旧环境自动降级为无动画。
@property --angle {
syntax: '<angle>';
initial-value: 0deg;
inherits: false;
}
.glow {
background: linear-gradient(var(--angle), #8b5cf6, #ec4899);
transition: --angle 0.6s; /* 现在可以过渡了 */
}
.glow:hover { --angle: 180deg; }var(--bp) 不能用在 @media 的条件表达式里——媒体查询在解析时求值,而变量是运行时按元素解析的,两者机制冲突。想做「响应式断点令牌」只能用容器查询或 CSS 预处理器的编译期变量替代。这不是 bug 而是设计使然。
/* 非法:变量不能出现在媒体查询条件里 */
:root { --bp: 768px; }
@media (min-width: var(--bp)) { } /* 整条规则被丢弃 */
/* 合法:变量用在媒体查询块内部 */
@media (min-width: 768px) {
:root { --gap-md: 24px; } /* 断点内重定义变量是推荐用法 */
}变量用得多不如用得稳,这几条是团队协作中最值得遵守的:
按这个顺序搭,半小时内能得到一个支持主题切换与响应式的令牌骨架:
不能。@media (min-width: var(--bp)) 会导致整条规则失效,因为媒体查询在解析期求值,变量是运行时按元素解析的。变量可以写在媒体查询块内部按断点重定义,这是官方支持且推荐的用法。
只在变量完全未定义时生效。若变量已定义但值非法(如颜色位置放了数字),属性会回退到 initial 或继承值,而不是 fallback。想要「值非法也兜底」需用 @property 注册类型或在校验层处理。
用 element.style.setProperty('--color-primary', '#8b5cf6')。设在 documentElement 上即全局生效,设在具体元素上则只影响该子树。拖动调色、实时预览等高频操作推荐这种细粒度方案。
变量沿 DOM 树继承且后代可覆盖,这是组件换肤的基础,但也意味着变量作用域是「位置」而非「文件」——在深层元素上定义的变量不会影响兄弟分支。排查变量不生效时,先在 DevTools computed 面板确认该元素实际解析到的值。