真正能一键切换主题的是 data-theme + CSS 自定义属性 + 预编译主题 CSS 的组合方案;Sass 变量仅在编译期生效,无法运行时切换。

不能靠 Sass 变量本身实现“一键运行时切换”,它只在编译期生效;真正能一键切换的,是 data-theme + CSS 自定义属性 + 预编译主题 CSS 的组合方案。
用 $themes Map 统一收口所有主题色
把 light/dark 等主题全部塞进一个顶层 map,结构必须扁平、键名用字符串、值为嵌套 map:
$themes: (
"light": (
"bg": #ffffff,
"text": #1f2937,
"primary": #007bff
),
"dark": (
"bg": #111827,
"text": #f9fafb,
"primary": #3b82f6
)
);
常见错误:漏引号(light → "light")、键名带空格或特殊字符、用变量当 key(map-get($themes, $theme) 在旧版 Sass 中不支持)。
- 所有颜色必须只写在这里,组件里禁止硬编码
#007bff或重复声明$primary-light - 新增主题只需往
$themes里加一项,无需改任何组件文件 - IDE 能跳转到 key 定义处,
map-get()错误时返回null而非崩溃,比拼接变量名安全得多
用 theme-color() 函数封装取值逻辑
别在每个 color: 后面手写 map-get(map-get($themes, "dark"), "text") —— 抽成函数,参数语义清晰:
立即学习“前端免费学习笔记(深入)”;
@function theme-color($theme-name, $key) {
$theme-map: map-get($themes, $theme-name);
@if $theme-map == null {
@error "Unknown theme: #{$theme-name}";
}
$value: map-get($theme-map, $key);
@if $value == null {
@error "Key '#{$key}' not found in theme '#{$theme-name}'";
}
@return $value;
}
调用时直接 color: theme-color("dark", "text");,可读性高,且编译后是静态值(无运行时开销)。
- 函数内部做了两级存在性校验,避免静默失败
- 不推荐用
@if $theme == "dark"分支写死逻辑——那会锁死主题数量,无法扩展 third-party 主题 - 别把函数放在组件文件里,统一收在
_functions.scss,用@use导入
用 @each 生成带命名空间的 CSS 自定义属性
想让 JS 控制切换?Sass 不负责运行时,但它可以为你把 $themes 编译成干净的 CSS 变量:
[data-theme="light"] {
--bg: #{map-get(map-get($themes, "light"), "bg")};
--text: #{map-get(map-get($themes, "light"), "text")};
}
[data-theme="dark"] {
--bg: #{map-get(map-get($themes, "dark"), "bg")};
--text: #{map-get(map-get($themes, "dark"), "text")};
}
关键点:#{...} 插值确保输出的是值(如 #ffffff),不是函数调用;作用域限定在 [data-theme="xxx"] 下,避免全局污染。
- 不要直接输出到
:root—— 多主题共存时会互相覆盖 - JS 切换只需
document.documentElement.setAttribute("data-theme", "dark"),CSS 自动响应 - 如果用了
prefers-color-scheme做默认回退,也得在 HTML 上提前写好初始data-theme,否则首次渲染可能闪白/闪黑
组件样式必须依赖主题参数而非默认值
组件 mixin 的 $theme 参数不能设默认值 default,否则编译器无法报错,容易漏传:
@mixin button($theme) {
background-color: theme-color($theme, "primary");
color: theme-color($theme, "text");
}
.btn-dark { @include button("dark"); }
.btn-light { @include button("light"); }
如果你写成 @mixin button($theme: default),又忘了传参,Sass 会静默使用 default 这个 key 去查 map —— 而 $themes 里根本没有叫 default 的主题,结果是编译报错或取到 null。
- 强制显式传参,是防止主题断裂最有效的约束
- 别在组件内部再做
@if $theme == "dark"分支——那会让样式逻辑和主题耦合,违背单一职责 - 构建时若需输出多份 CSS(
light.css/dark.css),入口文件应分别注入$theme: "light"并用@if控制输出,但这种模式和data-theme方案互斥,别混用
最易被忽略的是:Sass 编译结果和 JS 运行时是两套系统。你写了 color: var(--text, theme-color("light", "text")),看着很保险,但如果 JS 忘了设置 --text,回退值就固定成了 light 主题色,暗色模式下文字依然发黑——回退机制本身就有主题偏好,得按需设计。


















