CSS Handbook · 自定义属性与现代函数

第十章 / 10.1 CSS 自定义属性

第十章 · 10.1 节

CSS 自定义属性


自定义属性(俗称 CSS 变量)让"魔法数字"和重复的颜色值有了统一的出处。本教程站点的配色系统——纸张色、墨色、强调色,全部通过 :root 上的自定义属性定义,深浅色模式切换本质上就是重新赋值这些变量。

声明与使用

:root {
  --accent: #8a5a2e;
  --gap: 16px;
}
.btn {
  color: var(--accent);
  padding: var(--gap);
}

自定义属性以 -- 开头,取值可以是任意合法 CSS 片段,通过 var() 函数读取。它们区分大小写,且不会被浏览器做任何语法校验(直到被 var() 实际使用时才检查)。

自定义属性也会层叠,且可继承

与普通属性一样,自定义属性遵循层叠与继承规则——这正是它比 Sass 变量强大的地方:Sass 变量在编译时就被替换为固定值,而 CSS 变量在运行时按 DOM 树逐层生效,可以被任意层级的选择器覆盖。

.theme-warm { --accent: #b3452c; }
/* .theme-warm 内部所有引用 --accent 的元素,颜色自动切换,无需重复声明 */
效果预览:局部覆盖变量
默认 --accent
局部覆盖后

回退值

var() 的第二个参数是回退值,当变量未定义时生效,让组件在脱离主题上下文时也不至于样式崩坏:

.badge {
  background: var(--badge-color, #3f5d54);
}

与 JavaScript 联动

自定义属性可以被 JavaScript 读写,是连接样式系统与交互逻辑的桥梁,无需再用内联 style 拼接一长串样式字符串:

el.style.setProperty('--progress', '64%');
.bar::before {
  width: var(--progress, 0%);
  transition: width .3s ease;
}

@property:为变量声明类型

普通自定义属性的值在浏览器眼中只是一段"未经解析的文本",因此无法参与过渡动画(比如渐变角度、颜色不能平滑过渡)。@property 让我们显式声明变量的类型、初始值与是否可继承,赋予它真正参与插值计算的能力:

@property --angle {
  syntax: '<angle>';
  inherits: false;
  initial-value: 0deg;
}
.spin {
  background: conic-gradient(#8a5a2e var(--angle), #e7e8e1 0);
  transition: --angle .3s ease;
}
.spin:hover { --angle: 360deg; }

实践:设计令牌

大型项目中,建议把自定义属性组织为分层的"设计令牌"体系:底层是原始色值/间距(primitive),上层是带语义的别名(semantic),组件样式只引用语义层,替换主题时只需改动上层映射:

/* 原始层 */
:root { --brown-600: #8a5a2e; --teal-600: #3f5d54; }

/* 语义层:组件只认这一层 */
:root {
  --color-accent: var(--brown-600);
  --color-success: var(--teal-600);
}

Chapter 10 · Section 10.1

CSS Custom Properties


Custom properties (commonly known as CSS variables) give "magic numbers" and repeated color values a single source of truth. This tutorial site's color system — paper, ink, and accent colors — is defined entirely through custom properties on :root; switching between light and dark themes is essentially reassigning those variables.

Declaring and Using Custom Properties

:root {
  --accent: #8a5a2e;
  --gap: 16px;
}
.btn {
  color: var(--accent);
  padding: var(--gap);
}

Custom properties start with --, can hold any valid CSS fragment as their value, and are read with the var() function. They are case-sensitive, and the browser performs no syntax validation on them (until they are actually used in a var()).

Custom Properties Cascade Too, and Inherit

Like ordinary properties, custom properties follow the cascade and inheritance rules — that is exactly what makes them more powerful than Sass variables: Sass variables are replaced with fixed values at compile time, while CSS variables take effect at runtime, layer by layer down the DOM tree, and can be overridden by selectors at any level.

.theme-warm { --accent: #b3452c; }
/* every element inside .theme-warm that references --accent switches color automatically, with no redeclaration needed */
Live preview: locally overriding a variable
default --accent
after local override

Fallback Values

The second argument of var() is the fallback value, used when the variable is undefined, so components do not break when taken out of their theme context:

.badge {
  background: var(--badge-color, #3f5d54);
}

Working with JavaScript

Custom properties can be read and written by JavaScript, making them the bridge between the style system and interaction logic — no need to concatenate long style strings with inline styles:

el.style.setProperty('--progress', '64%');
.bar::before {
  width: var(--progress, 0%);
  transition: width .3s ease;
}

@property: Declaring a Type for a Variable

To the browser, an ordinary custom property's value is just "unparsed text", so it cannot participate in transition animations (for example, gradient angles and colors cannot transition smoothly). @property lets us explicitly declare the variable's type, initial value, and whether it inherits, giving it the ability to take part in interpolation:

@property --angle {
  syntax: '<angle>';
  inherits: false;
  initial-value: 0deg;
}
.spin {
  background: conic-gradient(#8a5a2e var(--angle), #e7e8e1 0);
  transition: --angle .3s ease;
}
.spin:hover { --angle: 360deg; }

In Practice: Design Tokens

In large projects, organize custom properties into a layered "design token" system: the bottom layer holds raw colors and spacing (primitive), the top layer holds semantic aliases (semantic), and component styles only reference the semantic layer — retheming only requires changing the top-level mappings:

/* primitive layer */
:root { --brown-600: #8a5a2e; --teal-600: #3f5d54; }

/* semantic layer: components only reference this layer */
:root {
  --color-accent: var(--brown-600);
  --color-success: var(--teal-600);
}