主题配置须用SCSS map统一管理,如$themes: (light: (bg: #fff, text: #1e293b), dark: (bg: #0f172a, text: #f1f5f9));所有主题key必须一致以支持map-get取值;@each配合[data-theme]生成选择器确保可维护性;CSS变量--text-color等实现运行时切换,JS通过setAttribute('data-theme')触发,而非直接操作style.setProperty;mixins中需用!global或theme-val函数确保$theme-map作用域正确。

主题配置必须用 SCSS map,不能用独立变量
硬写 $primary-light、$primary-dark 这类变量,主题一多就失控。新增一个主题得改十几处,漏一处就错色。真正可维护的做法是把每个主题当作一个“数据对象”存进 map:$themes: ( light: ( bg: #ffffff, text: #1e293b, primary: #4285f4 ), dark: ( bg: #0f172a, text: #f1f5f9, primary: #79bbff ) );
所有主题的 key(如 bg、text)必须完全一致——JS 读取时靠这个 key 取值,不一致就会 map-get($theme-map, text) 返回 null。
@each 必须配合 [data-theme] 生成选择器块
手写 [data-theme="dark"] .btn { color: #f1f5f9; } 这种规则,3 个主题还能忍,5 个以上必然漏或写错。SCSS 的 @each 是唯一靠谱方式:@each $theme-name, $theme-map in $themes { [data-theme="#{$theme-name}"] & { color: map-get($theme-map, text); } }
注意两点:
- & 保留父选择器上下文,适合在 mixin 里复用
- [data-theme] 是运行时切换的锚点,不是装饰用的,JS 必须通过 document.documentElement.setAttribute('data-theme', 'dark') 来触发
- 不要用 :root[data-theme] 直接覆盖 CSS 变量,那会破坏组件内联样式优先级,导致 color: var(--text-color) !important 失效
CSS 变量才是运行时切换的关键,SCSS 变量只负责编译期映射
$primary-color 在打包后就变成死值了,JS 改不了,也读不到。真正能动态换色的是 CSS 自定义属性::root { --text-color: #{map-get($themes, light, text)}; }[data-theme="dark"] { --text-color: #{map-get($themes, dark, text)}; }
然后组件里统一写 color: var(--text-color);,而不是 color: $text-color;。
常见错误:
- 把 document.documentElement.style.setProperty('--text-color', 'red') 当成主题切换——它只改单个变量,无法批量同步所有关联色
- 在 JS 里反复 setAttribute 切换 data-theme,但没配 localStorage 持久化,刷新就回退
- 忘了给 :root 加默认值,导致首次渲染时 var(--text-color) 取不到 fallback,显示为透明或继承色
mixins 封装要小心作用域和 !global
写 @mixin themeify { @each ... { @content; } } 很常见,但里面用到 map-get($theme-map, text) 时,$theme-map 是局部变量,@content 块里访问不到。必须加 $theme-map: $theme-map !global; 提升作用域。
更稳妥的做法是把取值逻辑收进函数:@function theme-val($key) { @return map-get($theme-map, $key); }
但前提是调用前已通过 !global 或外层作用域把 $theme-map 传进来。否则函数里查不到任何东西。
另一个坑:mixins 里写 !important 容易覆盖组件自身样式,比如 background-color: theme-val(bg) !important; 会让按钮 hover 色失效——除非你真需要强制覆盖,否则别加


















