语法是两条:声明 --名字: 值;,读取 var(--名字, 回退值)。它不是 Sass 变量——它在渲染时按元素在 DOM 树中的位置求解,所以同一个变量在不同子树可以有不同的值。
:root { --brand-500: #0969da; --radius-3: 10px; --space-3: 16px; }
/* 命名建议:类别-语义-刻度,避免 --blue / --big 这种「描述外观而非意图」的名字
换主题时你改的是「语义色」的取值,而不是把所有 .btn 的类名换一遍 */
作用域由声明它选择器决定::root 全站可见;写在组件类上只影响子树;写在 @media 里就是响应式变量。
这是一张卡片,用的是 --bg-2 / --line-1 / --text-1。
html[data-theme='dark'] { --bg-1: #0d1117; --text-1: #e6edf3; --brand-500: #6aa8ff; color-scheme: dark; }
/* 三件配套事项:
① 加 color-scheme,浏览器默认控件与滚动条一起换;
② 暗色下品牌色要降饱和、提明度(#0969da → #6aa8ff),不能简单取反;
③ JS 里读写 的 data-theme,并把用户选择存进 localStorage,首屏前用内联脚本应用,避免闪烁。 */
这段读 var(--c-not-defined, #bf8700):变量不存在时用回退值。
.x { color: var(--maybe, blue); } /* 回退可以是颜色关键字 */
.y { padding: var(--a, var(--b, 8px)); } /* 回退可以嵌套 */
.z { width: calc(var(--w, 100px) * 2); } /* 回退参与计算 */
/* 若变量未定义且没给回退,整条声明在该元素上「无效」(相当于 unset 到继承/初始值),不是报错 */
/* 陷阱:注册过的变量(见下)值为空时会走 guaranteed-invalid 分支,行为与未定义不同 */
这个容器写了 --badge-bg/--badge-fg,里面的 badge 全部换色,但没有任何 .badge--xxx 修饰符:
/* 这套写法叫「变量作为组件的公共 API」:
.badge { --badge-bg: …; --badge-fg: …; background: var(--badge-bg); }
使用方: —— 无需新增类就能定制,也无需 !important */
⑤ 用变量驱动计算与响应式
ABC
.grid-auto { --cols: 3; grid-template-columns: repeat(var(--cols), 1fr); }
.bar { --pct: 62%; } .bar > i { width: var(--pct); }
@media (max-width: 40rem) { :root { --cols: 1; --space-3: 12px; --header-h: 96px; } }
/* 把「断点决定的一组尺寸」集中成变量,比在每个规则里重复写媒体查询干净得多 */
⑥ 用 JS 读写变量
/* 读(解析后的值) */ getComputedStyle(el).getPropertyValue('--brand-500').trim();
/* 写在具体元素上(只影响这棵子树) */ el.style.setProperty('--pct', '80%');
/* 写回全局:删掉内联值,落回样式表 */ el.style.removeProperty('--pct');
/* 用自定义属性传递状态给 CSS(比切一堆类名更可组合):
→ .item { transition-delay: calc(var(--index) * 60ms); } */
上面两个按钮就是 setProperty / 改 data-theme 的效果。注意:变量本身不能被平滑过渡(浏览器不知道该插值成什么样),要动画请先用 @property 注册语法类型:
@property --pct { syntax: "<percentage>"; inherits: false; initial-value: 0%; }
.bar > i { width: var(--pct); transition: --pct .6s ease; } /* 注册后才能对变量做过渡/动画 */
⑦ 常见误区
误区 事实
变量是「常量」 它按元素逐个求解,会继承、会被就近覆盖
值必须是颜色/长度 可以是任意 token 序列,包括一整串 0 0 0 1px #fff 阴影值,甚至选择器片段(@scope/:is() 里谨慎用)
在 calc() 里可以随便乘变量 变量必须带单位才能参与长度计算;纯数字变量请写成 calc(var(--n) * 1px)
循环引用会崩 循环引用让相关声明「无效」(不报错、不崩溃,但排查很痛)