CSS 变量,正式名称为 CSS 自定义属性(CSS Custom Properties),从根本上改变了我们编写和维护样式表的方式。它们将变量的能力——以前只能通过 Sass 等预处理器使用——直接带入浏览器,并附带级联、继承和通过 JavaScript 运行时操作的额外优势。本完整 CSS 自定义属性指南涵盖从基本语法和作用域规则到动态主题、颜色系统、使用 @property 制作动画以及实际组件模式的所有内容。
1. 语法:声明和使用 CSS 变量
CSS 自定义属性遵循简单的两部分语法:使用双连字符前缀(--)声明,使用 var() 函数消费。var() 函数还支持可选的回退值,当属性未定义时使用。
声明自定义属性
/* Declaring CSS custom properties */
:root {
--color-primary: #3b82f6;
--color-secondary: #8b5cf6;
--font-size-base: 16px;
--spacing-unit: 8px;
--border-radius: 8px;
--font-family: 'Inter', system-ui, sans-serif;
--transition-speed: 200ms;
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
}
/* Using them in rules */
.button {
background-color: var(--color-primary);
font-size: var(--font-size-base);
padding: var(--spacing-unit) calc(var(--spacing-unit) * 2);
border-radius: var(--border-radius);
font-family: var(--font-family);
transition: background-color var(--transition-speed) ease;
box-shadow: var(--shadow-sm);
}使用 var() 和回退值
/* var() with fallback value */
.element {
/* If --accent is not defined, use #e11d48 */
color: var(--accent, #e11d48);
/* Fallback can be another variable */
background: var(--bg-card, var(--bg-primary, #ffffff));
/* Fallback with full value including commas */
font-family: var(--font-stack, 'Helvetica Neue', Arial, sans-serif);
}
/* Common pattern: fallback to a default theme */
.card {
background: var(--card-bg, var(--surface-color, #1e293b));
color: var(--card-text, var(--text-primary, #f8fafc));
padding: var(--card-padding, 24px);
}嵌套回退和组合
/* Composing variables with calc() */
:root {
--base: 8px;
--scale: 1.5;
}
.component {
/* Math with variables */
padding: calc(var(--base) * 2); /* 16px */
margin-bottom: calc(var(--base) * 3); /* 24px */
gap: calc(var(--base) * var(--scale)); /* 12px */
}
/* String-like composition (for building values) */
:root {
--h: 220;
--s: 90%;
--l: 56%;
}
.element {
/* Build a full HSL color from parts */
color: hsl(var(--h), var(--s), var(--l));
/* Darken by adjusting lightness */
border-color: hsl(var(--h), var(--s), calc(var(--l) - 10%));
}2. 作用域与级联
与 Sass 变量在编译时解析不同,CSS 自定义属性参与级联。它们默认继承,可以作用域限定到任何选择器,并且可以在 DOM 树的任何级别被覆盖。
使用 :root 的全局变量
/* :root = <html> element — highest specificity for globals */
:root {
--color-brand: #3b82f6;
--color-danger: #ef4444;
--color-success: #22c55e;
--color-warning: #f59e0b;
--text-primary: #f8fafc;
--text-secondary: #94a3b8;
--bg-primary: #0f172a;
--bg-secondary: #1e293b;
--bg-card: #1e293b;
}
/* Every element in the page can now use these */
body {
color: var(--text-primary);
background: var(--bg-primary);
}组件级作用域
/* Variables scoped to a component */
.alert {
--alert-bg: #1e293b;
--alert-border: #334155;
--alert-text: #e2e8f0;
--alert-icon-size: 20px;
background: var(--alert-bg);
border: 1px solid var(--alert-border);
color: var(--alert-text);
padding: 16px;
border-radius: 8px;
}
/* Variants override the component-scoped variables */
.alert--danger {
--alert-bg: #450a0a;
--alert-border: #ef4444;
--alert-text: #fecaca;
}
.alert--success {
--alert-bg: #052e16;
--alert-border: #22c55e;
--alert-text: #bbf7d0;
}
.alert--warning {
--alert-bg: #451a03;
--alert-border: #f59e0b;
--alert-text: #fef3c7;
}继承与覆盖
自定义属性像 color 或 font-size 一样沿 DOM 树向下继承。子元素可以读取任何祖先设置的变量,任何元素都可以为其子树覆盖变量。
/* Parent sets the variable */
.sidebar {
--link-color: #60a5fa;
}
/* Child inherits it */
.sidebar a {
color: var(--link-color); /* #60a5fa */
}
/* Grandchild can override */
.sidebar .promo-section {
--link-color: #f59e0b; /* override for this subtree */
}
.sidebar .promo-section a {
color: var(--link-color); /* #f59e0b */
}
/* Variables NOT inherited by siblings or parents */
.main-content a {
/* This will NOT be #60a5fa — falls back to default */
color: var(--link-color, #3b82f6);
}3. 动态主题
CSS 变量最强大的应用之一是动态主题。通过更改几个变量值,你可以完全改变网站的外观——无需在各个元素上切换类名。
使用 CSS 变量切换深色模式
/* Light theme (default) */
:root {
--bg-primary: #ffffff;
--bg-secondary: #f1f5f9;
--bg-card: #ffffff;
--text-primary: #0f172a;
--text-secondary: #64748b;
--border-color: #e2e8f0;
--shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
/* Dark theme — toggled via class on <html> or <body> */
:root.dark {
--bg-primary: #0f172a;
--bg-secondary: #1e293b;
--bg-card: #1e293b;
--text-primary: #f8fafc;
--text-secondary: #94a3b8;
--border-color: #334155;
--shadow: 0 1px 3px rgba(0, 0, 0, 0.4);
}
/* All components use the variables — theme changes automatically */
body { background: var(--bg-primary); color: var(--text-primary); }
.card { background: var(--bg-card); border: 1px solid var(--border-color); box-shadow: var(--shadow); }
.text-muted { color: var(--text-secondary); }// Toggle dark mode with JavaScript
const toggle = document.getElementById('theme-toggle');
toggle.addEventListener('click', () => {
document.documentElement.classList.toggle('dark');
// Persist preference
const isDark = document.documentElement.classList.contains('dark');
localStorage.setItem('theme', isDark ? 'dark' : 'light');
});
// Apply saved preference on load
if (localStorage.getItem('theme') === 'dark') {
document.documentElement.classList.add('dark');
}prefers-color-scheme 媒体查询
/* Auto-detect OS/browser dark mode preference */
@media (prefers-color-scheme: dark) {
:root {
--bg-primary: #0f172a;
--bg-secondary: #1e293b;
--bg-card: #1e293b;
--text-primary: #f8fafc;
--text-secondary: #94a3b8;
--border-color: #334155;
}
}
/* Combine with manual override */
@media (prefers-color-scheme: dark) {
:root:not(.light) {
--bg-primary: #0f172a;
--text-primary: #f8fafc;
/* ... dark values */
}
}
@media (prefers-color-scheme: light) {
:root:not(.dark) {
--bg-primary: #ffffff;
--text-primary: #0f172a;
/* ... light values */
}
}多主题切换
/* Define multiple themes using data attributes */
:root,
[data-theme="default"] {
--brand: #3b82f6;
--brand-light: #93c5fd;
--accent: #8b5cf6;
}
[data-theme="sunset"] {
--brand: #f97316;
--brand-light: #fdba74;
--accent: #ef4444;
}
[data-theme="forest"] {
--brand: #22c55e;
--brand-light: #86efac;
--accent: #14b8a6;
}
[data-theme="ocean"] {
--brand: #0ea5e9;
--brand-light: #7dd3fc;
--accent: #6366f1;
}// Switch themes from JavaScript
function setTheme(themeName) {
document.documentElement.setAttribute('data-theme', themeName);
localStorage.setItem('theme', themeName);
}
// Usage
setTheme('sunset');
setTheme('forest');
setTheme('default');4. 使用 CSS 变量构建颜色系统
CSS 变量非常擅长构建系统化的调色板。HSL 颜色模型特别强大,因为你可以独立操作色相、饱和度和明度。
HSL 方法构建调色板
/* HSL-based color palette — change one hue to update entire palette */
:root {
/* Primary color — change --primary-h to shift the whole palette */
--primary-h: 220;
--primary-s: 90%;
--primary-50: hsl(var(--primary-h), var(--primary-s), 97%);
--primary-100: hsl(var(--primary-h), var(--primary-s), 93%);
--primary-200: hsl(var(--primary-h), var(--primary-s), 83%);
--primary-300: hsl(var(--primary-h), var(--primary-s), 73%);
--primary-400: hsl(var(--primary-h), var(--primary-s), 63%);
--primary-500: hsl(var(--primary-h), var(--primary-s), 53%); /* base */
--primary-600: hsl(var(--primary-h), var(--primary-s), 43%);
--primary-700: hsl(var(--primary-h), var(--primary-s), 33%);
--primary-800: hsl(var(--primary-h), var(--primary-s), 23%);
--primary-900: hsl(var(--primary-h), var(--primary-s), 13%);
/* Secondary — just change hue */
--secondary-h: 270;
--secondary-s: 80%;
--secondary-500: hsl(var(--secondary-h), var(--secondary-s), 53%);
}
/* To create a completely different color scheme, change one value: */
:root.warm {
--primary-h: 15; /* orange */
}语义化颜色令牌
/* Map raw colors to semantic tokens */
:root {
/* Raw palette (primitive tokens) */
--blue-500: #3b82f6;
--blue-600: #2563eb;
--red-500: #ef4444;
--green-500: #22c55e;
--gray-100: #f1f5f9;
--gray-800: #1e293b;
--gray-900: #0f172a;
/* Semantic tokens (reference tokens) */
--color-action: var(--blue-500);
--color-action-hover: var(--blue-600);
--color-error: var(--red-500);
--color-success: var(--green-500);
--color-bg: var(--gray-900);
--color-surface: var(--gray-800);
--color-text: var(--gray-100);
}
/* Components use semantic tokens, never raw colors */
.btn-primary {
background: var(--color-action);
}
.btn-primary:hover {
background: var(--color-action-hover);
}
.error-message {
color: var(--color-error);
}Alpha / 透明度变化
/* Modern approach: store components, compose in usage */
:root {
--brand-rgb: 59, 130, 246;
--danger-rgb: 239, 68, 68;
}
.overlay {
background: rgba(var(--brand-rgb), 0.1); /* 10% opacity */
}
.overlay-hover:hover {
background: rgba(var(--brand-rgb), 0.2); /* 20% opacity */
}
.border-subtle {
border: 1px solid rgba(var(--brand-rgb), 0.3);
}
/* Modern CSS: using color-mix() (no RGB splitting needed) */
:root {
--brand: #3b82f6;
}
.element {
/* 20% brand color mixed with transparent */
background: color-mix(in srgb, var(--brand) 20%, transparent);
/* 50% brand color */
border-color: color-mix(in srgb, var(--brand) 50%, transparent);
}5. 间距与排版系统
一致的间距比例和流体排版系统是任何设计良好界面的基础。CSS 变量使两者都易于定义、维护和覆盖。
间距比例
/* 8px-based spacing scale */
:root {
--space-1: 4px; /* 0.25rem */
--space-2: 8px; /* 0.5rem */
--space-3: 12px; /* 0.75rem */
--space-4: 16px; /* 1rem */
--space-5: 20px; /* 1.25rem */
--space-6: 24px; /* 1.5rem */
--space-8: 32px; /* 2rem */
--space-10: 40px; /* 2.5rem */
--space-12: 48px; /* 3rem */
--space-16: 64px; /* 4rem */
--space-20: 80px; /* 5rem */
}
/* Usage: consistent spacing everywhere */
.card {
padding: var(--space-6);
gap: var(--space-4);
}
.section {
padding-block: var(--space-16);
}
.stack > * + * {
margin-top: var(--space-4);
}使用 clamp() 的流体排版
/* Fluid typography: scales between viewport widths */
:root {
/* clamp(min, preferred, max) */
--text-sm: clamp(0.8rem, 0.17vw + 0.76rem, 0.89rem);
--text-base: clamp(1rem, 0.34vw + 0.91rem, 1.19rem);
--text-lg: clamp(1.25rem, 0.61vw + 1.1rem, 1.58rem);
--text-xl: clamp(1.56rem, 1vw + 1.31rem, 2.11rem);
--text-2xl: clamp(1.95rem, 1.56vw + 1.56rem, 2.81rem);
--text-3xl: clamp(2.44rem, 2.38vw + 1.85rem, 3.75rem);
}
h1 { font-size: var(--text-3xl); }
h2 { font-size: var(--text-2xl); }
h3 { font-size: var(--text-xl); }
p { font-size: var(--text-base); }
small { font-size: var(--text-sm); }字体比例系统
/* Modular type scale using calc() */
:root {
--type-base: 1rem;
--type-ratio: 1.25; /* Major third scale */
--type-sm: calc(var(--type-base) / var(--type-ratio));
--type-md: var(--type-base);
--type-lg: calc(var(--type-base) * var(--type-ratio));
--type-xl: calc(var(--type-base) * var(--type-ratio) * var(--type-ratio));
--type-2xl: calc(var(--type-base) * var(--type-ratio) * var(--type-ratio) * var(--type-ratio));
--leading-tight: 1.2;
--leading-normal: 1.5;
--leading-relaxed: 1.75;
}
/* Responsive: change base and ratio, everything scales */
@media (min-width: 1024px) {
:root {
--type-base: 1.125rem;
--type-ratio: 1.333; /* Perfect fourth on large screens */
}
}6. CSS 变量 vs Sass 变量
CSS 自定义属性和 Sass 变量都用于减少重复,但它们的工作方式根本不同。选择并非非此即彼——许多项目同时使用两者。
对比表
| 特性 | CSS 自定义属性 | Sass 变量 |
|---|---|---|
| 解析时机 | 运行时(浏览器) | 编译时(构建) |
| 级联 | 是 — 继承、可覆盖 | 否 — 扁平作用域 |
| JS 访问 | 是 — 可从 JS 读写 | 否 — 编译后消失 |
| 媒体查询 | 是 — 可在 @media 中更改 | 仅编译时 |
| 函数 | 仅 calc() | 丰富的函数、循环、混入 |
| 浏览器支持 | 97%+(所有现代浏览器) | 编译为 CSS — 通用 |
| 性能 | 微小的运行时开销 | 零运行时开销 |
何时使用哪个
使用 CSS 变量处理运行时变化的内容:主题、响应式调整、用户偏好、组件 API 接口。使用 Sass 变量处理静态设计令牌、构建期间的复杂计算、条件编译和生成实用工具类。最佳实践:将设计令牌定义为 Sass 变量,然后将它们暴露为 CSS 自定义属性以获得运行时灵活性。
/* Best practice: Sass variables → CSS custom properties */
/* _tokens.scss (Sass source) */
$color-primary: #3b82f6;
$color-secondary: #8b5cf6;
$spacing-base: 8px;
$font-stack: 'Inter', system-ui, sans-serif;
$breakpoint-md: 768px;
/* Expose as CSS custom properties */
:root {
--color-primary: #{$color-primary};
--color-secondary: #{$color-secondary};
--spacing-base: #{$spacing-base};
--font-stack: #{$font-stack};
}
/* Use Sass for build-time logic */
@for $i from 1 through 8 {
.space-#{$i} {
--space: calc(var(--spacing-base) * #{$i});
margin: var(--space);
}
}
/* Use CSS variables for runtime values */
.component {
color: var(--color-primary);
font-family: var(--font-stack);
}7. JavaScript 交互
CSS 自定义属性可以从 JavaScript 读取和写入,使其成为 CSS 和 JS 之间的强大桥梁。这使得动态样式设置无需在每个元素上管理内联样式。
从 JS 读取 CSS 变量
// Read a CSS variable from :root
const root = document.documentElement;
const styles = getComputedStyle(root);
const primaryColor = styles.getPropertyValue('--color-primary').trim();
console.log(primaryColor); // "#3b82f6"
// Read from a specific element
const card = document.querySelector('.card');
const cardBg = getComputedStyle(card).getPropertyValue('--card-bg').trim();
// Read all CSS variables on an element
function getCSSVariables(element) {
const styles = getComputedStyle(element);
const vars = {};
for (const prop of styles) {
if (prop.startsWith('--')) {
vars[prop] = styles.getPropertyValue(prop).trim();
}
}
return vars;
}从 JS 写入 CSS 变量
// Set a CSS variable on :root (affects entire page)
document.documentElement.style.setProperty('--color-primary', '#e11d48');
// Set on a specific element (affects element + descendants)
const sidebar = document.querySelector('.sidebar');
sidebar.style.setProperty('--sidebar-width', '300px');
// Remove a custom property (revert to inherited/default)
document.documentElement.style.removeProperty('--color-primary');
// Dynamic theming based on user input
const colorPicker = document.getElementById('brand-color');
colorPicker.addEventListener('input', (e) => {
document.documentElement.style.setProperty('--brand', e.target.value);
});
// Set multiple variables at once
function applyTheme(theme) {
const root = document.documentElement.style;
Object.entries(theme).forEach(([key, value]) => {
root.setProperty(key, value);
});
}
applyTheme({
'--bg-primary': '#0f172a',
'--text-primary': '#f8fafc',
'--brand': '#3b82f6',
});响应式模式
// React: CSS variables as component API
function Card({ accentColor, padding, children }) {
return (
<div
className="card"
style={{
'--card-accent': accentColor,
'--card-padding': padding,
}}
>
{children}
</div>
);
}
// Vue 3: v-bind in <style>
// <style scoped>
// .element {
// color: v-bind('themeColor'); /* compiles to CSS variable */
// }
// </style>
// Vanilla JS: Respond to scroll position
window.addEventListener('scroll', () => {
const scrollPercent = window.scrollY / (document.body.scrollHeight - window.innerHeight);
document.documentElement.style.setProperty('--scroll', scrollPercent);
});
// CSS uses the variable
// .progress-bar { transform: scaleX(var(--scroll)); }
// .parallax { transform: translateY(calc(var(--scroll) * -50px)); }8. 使用 CSS 变量制作动画
默认情况下,CSS 自定义属性无法制作动画,因为浏览器不知道它们持有什么类型的值。@property 规则通过注册具有特定语法的属性来解决这个问题。
问题:为什么变量无法动画化
/* This does NOT work — browser treats variables as strings */
:root {
--box-width: 100px;
}
.box {
width: var(--box-width);
transition: width 0.3s ease; /* ← will NOT animate */
}
.box:hover {
--box-width: 300px; /* Jumps instantly, no transition */
}
/* Why? The browser sees: transition on 'width', but the value
change happens to '--box-width' (a string). The browser
doesn't know --box-width contains a <length>. */解决方案:@property 注册
/* Register the property with @property — now it animates! */
@property --box-width {
syntax: '<length>';
initial-value: 100px;
inherits: false;
}
.box {
width: var(--box-width);
transition: --box-width 0.3s ease; /* transition the PROPERTY itself */
}
.box:hover {
--box-width: 300px; /* Now smoothly animates! */
}
/* Animated color shift */
@property --glow-color {
syntax: '<color>';
initial-value: #3b82f6;
inherits: false;
}
.glow-button {
box-shadow: 0 0 20px var(--glow-color);
transition: --glow-color 0.5s ease;
}
.glow-button:hover {
--glow-color: #ec4899; /* Smooth color transition in shadow */
}动画渐变背景
/* Animate gradient positions with @property */
@property --gradient-angle {
syntax: '<angle>';
initial-value: 0deg;
inherits: false;
}
@property --color-1 {
syntax: '<color>';
initial-value: #3b82f6;
inherits: false;
}
@property --color-2 {
syntax: '<color>';
initial-value: #8b5cf6;
inherits: false;
}
.animated-gradient {
background: linear-gradient(
var(--gradient-angle),
var(--color-1),
var(--color-2)
);
animation: gradient-rotate 4s linear infinite;
}
@keyframes gradient-rotate {
0% { --gradient-angle: 0deg; --color-1: #3b82f6; --color-2: #8b5cf6; }
33% { --gradient-angle: 120deg; --color-1: #ec4899; --color-2: #f59e0b; }
66% { --gradient-angle: 240deg; --color-1: #22c55e; --color-2: #06b6d4; }
100% { --gradient-angle: 360deg; --color-1: #3b82f6; --color-2: #8b5cf6; }
}9. @property 规则
@property 规则是 CSS Houdini 规范的一部分。它允许你正式注册一个自定义属性,包含类型、初始值和继承行为。这解锁了动画和验证功能。
完整语法
@property --property-name {
syntax: '<type>'; /* Required: what type of value this holds */
initial-value: value; /* Required (if inherits: false): default value */
inherits: true | false; /* Required: does it inherit down the DOM? */
}
/* Examples */
@property --my-color {
syntax: '<color>';
initial-value: #3b82f6;
inherits: true;
}
@property --progress {
syntax: '<percentage>';
initial-value: 0%;
inherits: false;
}
@property --columns {
syntax: '<integer>';
initial-value: 3;
inherits: false;
}支持的类型
/* All supported syntax types for @property */
'<length>' /* 10px, 2rem, 50vh */
'<number>' /* 1.5, 42, 0.8 */
'<percentage>' /* 50%, 100% */
'<length-percentage>'/* 10px or 50% */
'<color>' /* #fff, rgb(), hsl(), named colors */
'<image>' /* url(), gradient functions */
'<url>' /* url(...) */
'<integer>' /* 1, 2, 3 (whole numbers) */
'<angle>' /* 45deg, 0.5turn, 100grad */
'<time>' /* 200ms, 1.5s */
'<resolution>' /* 96dpi, 2dppx */
'<transform-function>' /* rotate(), scale(), translate() */
'<transform-list>' /* multiple transforms */
'<custom-ident>' /* any identifier */
'*' /* any value (universal) */
/* Combining types */
'<length> | <percentage>' /* either type */
'<color>+' /* space-separated list of colors */
'<length>#' /* comma-separated list of lengths */实用的类型化属性
/* Circular progress indicator */
@property --progress {
syntax: '<percentage>';
initial-value: 0%;
inherits: false;
}
.circular-progress {
--progress: 0%;
background: conic-gradient(
var(--color-primary) var(--progress),
var(--bg-secondary) var(--progress)
);
border-radius: 50%;
width: 100px;
height: 100px;
transition: --progress 1s ease-out;
}
.circular-progress.loaded {
--progress: 75%;
}
/* Counting animation */
@property --num {
syntax: '<integer>';
initial-value: 0;
inherits: false;
}
.counter {
animation: count-up 2s ease-out forwards;
counter-reset: num var(--num);
}
.counter::after {
content: counter(num);
}
@keyframes count-up {
to { --num: 1000; }
}10. 组件模式
CSS 变量为组件创建了清晰的 API。消费者从外部设置变量,而不是覆盖内部选择器。此模式适用于任何框架或纯 HTML。
按钮组件
/* Button with CSS variable API */
.btn {
/* Internal defaults — consumers override these */
--btn-bg: #3b82f6;
--btn-color: #ffffff;
--btn-border: transparent;
--btn-radius: 8px;
--btn-padding-x: 20px;
--btn-padding-y: 10px;
--btn-font-size: 0.875rem;
--btn-hover-bg: color-mix(in srgb, var(--btn-bg), black 15%);
display: inline-flex;
align-items: center;
gap: 8px;
padding: var(--btn-padding-y) var(--btn-padding-x);
background: var(--btn-bg);
color: var(--btn-color);
border: 2px solid var(--btn-border);
border-radius: var(--btn-radius);
font-size: var(--btn-font-size);
cursor: pointer;
transition: background 200ms ease;
}
.btn:hover {
background: var(--btn-hover-bg);
}
/* Variants just override variables */
.btn-secondary {
--btn-bg: transparent;
--btn-color: #3b82f6;
--btn-border: #3b82f6;
--btn-hover-bg: rgba(59, 130, 246, 0.1);
}
.btn-danger {
--btn-bg: #ef4444;
--btn-hover-bg: #dc2626;
}
.btn-lg {
--btn-padding-x: 32px;
--btn-padding-y: 14px;
--btn-font-size: 1rem;
}卡片组件
/* Card component with variable API */
.card {
--card-bg: var(--bg-card, #1e293b);
--card-border: var(--border-color, #334155);
--card-radius: 12px;
--card-padding: 24px;
--card-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
--card-hover-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.2);
background: var(--card-bg);
border: 1px solid var(--card-border);
border-radius: var(--card-radius);
padding: var(--card-padding);
box-shadow: var(--card-shadow);
transition: box-shadow 200ms ease, transform 200ms ease;
}
.card:hover {
box-shadow: var(--card-hover-shadow);
transform: translateY(-2px);
}
.card-compact {
--card-padding: 16px;
--card-radius: 8px;
}
.card-featured {
--card-border: var(--color-primary, #3b82f6);
--card-shadow: 0 4px 6px -1px rgba(59, 130, 246, 0.15);
--card-hover-shadow: 0 10px 15px -3px rgba(59, 130, 246, 0.25);
}表单输入组件
/* Form input with variable API */
.input {
--input-bg: var(--bg-secondary, #1e293b);
--input-border: var(--border-color, #334155);
--input-text: var(--text-primary, #f8fafc);
--input-placeholder: var(--text-secondary, #64748b);
--input-focus-border: var(--color-primary, #3b82f6);
--input-radius: 8px;
--input-padding: 10px 14px;
--input-font-size: 0.875rem;
width: 100%;
padding: var(--input-padding);
background: var(--input-bg);
border: 2px solid var(--input-border);
border-radius: var(--input-radius);
color: var(--input-text);
font-size: var(--input-font-size);
outline: none;
transition: border-color 200ms ease, box-shadow 200ms ease;
}
.input::placeholder {
color: var(--input-placeholder);
}
.input:focus {
border-color: var(--input-focus-border);
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15);
}
/* Error state */
.input-error {
--input-border: var(--color-error, #ef4444);
--input-focus-border: var(--color-error, #ef4444);
}
/* Success state */
.input-success {
--input-border: var(--color-success, #22c55e);
--input-focus-border: var(--color-success, #22c55e);
}11. 浏览器支持与回退
CSS 自定义属性在所有现代浏览器中都受支持。但如果你需要支持旧版浏览器,有几种回退策略。
当前浏览器支持
截至 2025 年,CSS 自定义属性拥有超过 97% 的全球支持率。它们在 Chrome 49+、Firefox 31+、Safari 9.1+、Edge 15+ 和所有基于 Chromium 的浏览器中工作。@property 规则支持范围更有限(Chrome 85+、Safari 15.4+、Firefox 128+)。
回退策略
/* Strategy 1: Declare fallback value before the variable usage */
.element {
/* Old browsers use this */
color: #3b82f6;
/* Modern browsers override with the variable */
color: var(--color-primary, #3b82f6);
}
/* Strategy 2: Cascade fallback */
.card {
background: #1e293b; /* fallback for IE */
background: var(--bg-card, #1e293b); /* modern browsers */
border: 1px solid #334155;
border: 1px solid var(--border-color, #334155);
}
/* Strategy 3: PostCSS plugin (build-time) */
/* postcss-custom-properties compiles var() to static values */
/* Input: */ .box { color: var(--brand); }
/* Output: */ .box { color: #3b82f6; color: var(--brand); }使用 @supports
/* Feature detection with @supports */
@supports (--css: variables) {
/* CSS variables are supported */
:root {
--color-primary: #3b82f6;
}
.element {
color: var(--color-primary);
}
}
@supports not (--css: variables) {
/* Fallback for browsers without CSS variable support */
.element {
color: #3b82f6;
}
}
/* JavaScript feature detection */
// if (window.CSS && window.CSS.supports &&
// window.CSS.supports('--test', '0')) {
// // CSS variables supported
// }
/* @property feature detection */
@supports (syntax: '<color>') {
@property --animated-color {
syntax: '<color>';
initial-value: #3b82f6;
inherits: false;
}
}12. 常见问题
CSS 变量和 CSS 自定义属性有什么区别?
它们是同一个东西。"CSS 自定义属性"(CSS Custom Properties)是官方 W3C 规范名称,而 "CSS 变量"是常用的非正式名称。正式规范称为"用于级联变量的 CSS 自定义属性"。当人们说"CSS 变量"时,他们指的是用 -- 前缀声明并通过 var() 函数访问的属性。
CSS 自定义属性可以做动画或过渡吗?
默认不可以。CSS 自定义属性被视为字符串,因此浏览器无法在值之间插值。但是,使用 @property 规则(CSS Houdini 的一部分),你可以注册具有特定语法(如 <length>、<color> 或 <number>)的属性。注册后,浏览器知道类型并可以平滑地在值之间做动画或过渡。这在 Chrome、Edge、Safari 15.4+ 和 Firefox 128+ 中有效。
CSS 变量会影响性能吗?
对于典型用例,CSS 变量的性能影响可以忽略不计。浏览器在样式计算期间解析变量引用,与硬编码值相比增加了微小的开销。但是,深度嵌套的变量引用(var(--a) 引用 var(--b) 引用 var(--c)...)可能会增加计算时间。实际上,即使是拥有数百个变量的复杂设计系统也能表现良好。它们提供的运行时灵活性远远超过了最小的性能成本。
我可以在媒体查询或选择器中使用 CSS 变量吗?
你可以在媒体查询内部更改 CSS 变量的值(例如 @media (max-width: 768px) { :root { --font-size: 14px; } }),它会自动更新使用该变量的所有地方。但是,你不能在媒体查询条件本身中使用 var()(例如 @media (max-width: var(--breakpoint)) 不起作用)。你也不能在选择器字符串中使用 var()。变量只能在属性值中使用。
CSS 变量如何与 Web Components 的 Shadow DOM 配合工作?
CSS 自定义属性是少数几种能穿透 Shadow DOM 边界的样式机制之一。虽然常规 CSS 选择器无法到达 shadow root 内部,但继承的自定义属性会自然地流入。这使得 CSS 变量成为 Web Components 主题化的推荐 API:宿主页面设置变量(--button-bg、--button-color),组件内部样式使用 var(--button-bg, defaultValue) 来读取它们。这提供了清晰、明确的样式 API,而不会破坏封装性。
CSS 自定义属性已经改变了我们在 Web 上处理样式的方式。它们将变量、主题和动态样式功能原生地带入浏览器。结合 @property 实现类型安全的动画和 JavaScript 互操作性,它们是每个现代 Web 开发者的必备工具。