变量必须分层定义:基础token仅在:root声明并带语义前缀;组件禁用新变量声明,仅通过var()引用;暗色模式须用prefers-color-scheme媒体查询重置整套token,手动切换时需严格保持变量名与结构一致。

变量必须分层定义,不能在组件里直接写 --color-primary
直接在 .button 或 :root 里零散声明 --color-primary、--spacing-md,会导致主题切换时要改十几处,暗色模式要重写整套变量,第三方组件无法继承你的体系。根本问题不是变量没用,是变量没分层。
实操建议:
- 所有基础 Token(颜色、圆角、阴影、断点)只定义在
:root,且命名带语义前缀,比如--color-bg-surface而不是--bg-1 - 组件内部禁止声明新变量,只用
var(--color-bg-surface);需要局部覆盖时,用属性选择器或自定义属性传入,例如button[data-variant="ghost"] { --color-bg-surface: transparent; } - 禁止在 SCSS/Less 中用
@mixin包裹变量赋值——那会切断运行时响应能力,var()的动态优势就废了
var(--font-size-sm) 为什么在不同组件里“套不上”
全局设 --font-size-sm: 0.75rem 看似省事,但 Card 标题用它太小,Badge 用它又撑不开。关键不是调数值,而是定义相对关系:大小应由组件角色决定,而非固定像素。
实操建议:
- 建立两级字号系统:基础 Token(
--font-size-100、--font-size-200)对应绝对值;组件 Token(--text-body-font-size、--text-caption-font-size)用var(--font-size-100)引用,便于统一缩放 - 在组件根元素上通过
font-size缩放整个子树,比如.badge { font-size: var(--font-size-100); },其内部span就可用em或rem得到响应式缩放 - 避免在媒体查询里重复写
--font-size-sm,改用@media (min-width: 768px) { :root { --font-size-scale: 1.1; } },再让组件用calc(var(--font-size-100) * var(--font-size-scale))
暗色模式不能靠 JS 切类名就完事
仅靠 document.body.classList.toggle('dark-theme') 并在 .dark-theme 下重写一堆变量,容易漏掉嵌套组件、第三方库样式、伪元素颜色等。更严重的是,它破坏了变量继承链的可预测性。
实操建议:
- 暗色模式必须用
@media (prefers-color-scheme: dark)+:root重置整套 Token,确保所有层级自动响应系统偏好 - 若需手动触发(如用户开关),用 JavaScript 修改
document.documentElement.style.setProperty(),但变量名和结构必须与媒体查询完全一致,否则会出现不一致渲染 - 外部项目引入你的设计系统时,必须显式引入
tokens.css,并禁用 CSS 压缩工具的discardUnused选项,否则未被当前页面直接使用的变量会被误删
组件如何安全接收外部样式定制
用户想改某个 Button 的主色,又不想污染全局 --color-primary,硬塞 style="--color-primary: red" 会破坏继承链,且无法响应主题切换。
实操建议:
- 用
<var-style-provider></var-style-provider>类型的包裹组件(本质是带style属性的div),把定制变量注入作用域,仅影响其后代 - 组件自身支持
data-*属性驱动变量覆盖,例如<button data-color="success"></button>→button[data-color="success"] { --color-bg-surface: var(--color-success); } - 对外暴露
style-varsprop 时,必须经过formatStyleVars()这类工具函数清洗,过滤非法键名、转义值,防止 XSS 或样式注入
真正难的不是定义变量,而是守住变量的作用域边界和语义一致性。一旦在组件内部开始写 -- 开头的声明,设计系统就已开始失控。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











