自定义属性:能级联、能继承、能在运行时改的「带名字的变量」

语法是两条:声明 --名字: 值;,读取 var(--名字, 回退值)。它不是 Sass 变量——它在渲染时按元素在 DOM 树中的位置求解,所以同一个变量在不同子树可以有不同的值。

① 命名与作用域

:root { --brand-500: #0969da; --radius-3: 10px; --space-3: 16px; }
/* 命名建议:类别-语义-刻度,避免 --blue / --big 这种「描述外观而非意图」的名字
   换主题时你改的是「语义色」的取值,而不是把所有 .btn 的类名换一遍 */

作用域由声明它选择器决定::root 全站可见;写在组件类上只影响子树;写在 @media 里就是响应式变量。

② 主题化:只改变量,一条规则都不动

这是一张卡片,用的是 --bg-2 / --line-1 / --text-1。

chip 用 color-mix 自动派生底色
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 --warn 修饰符覆盖

这个容器写了 --badge-bg/--badge-fg,里面的 badge 全部换色,但没有任何 .badge--xxx 修饰符:

在 .zone 里的 badge修饰符优先级更高
/* 这套写法叫「变量作为组件的公共 API」:
   .badge { --badge-bg: …; --badge-fg: …; background: var(--badge-bg); }
   使用方:  
—— 无需新增类就能定制,也无需 !important */

⑤ 用变量驱动计算与响应式

A
B
C
.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)
循环引用会崩循环引用让相关声明「无效」(不报错、不崩溃,但排查很痛)