想把多主题切换做得干净又轻量,其实用 CSS 变量配合 theme.extend,再加上 darkMode: 'class' 就够了,没必要额外设插件;做法也不复杂:先在 :root 和 [data-theme="xxx"] 里把变量定义好,再通过 extend.colors 映射到 Tailwind,运行时只要修改 html 的 data-theme 属性,就能完成主题切换,并且还能顺手把状态持久化下来。

直接用 CSS 变量 + theme.extend + darkMode: 'class' 就能实现多主题切换,不需要插件,也不用重写构建流程。
为什么不用 tailwindcss-theming 这类插件
这类插件会劫持 theme.colors 解析逻辑,导致几个实际问题:
bg-primary在不同主题下可能指向不同层级的变量,DevTools 里点不到最终--color-primary值- 和
@apply、group-hover:等组合时容易丢失上下文,生成冗余 CSS - Tailwind 4 升级后大概率失效——插件依赖内部 AST 解析细节,最新不承诺这部分 API 稳定
在 :root 和 [data-theme] 中定义 CSS 变量
把每套主题色全部收口到 CSS 变量中,用 class 作用域隔离:
:root {
--color-primary: #1E90FF;
--color-bg: #FFFFFF;
--color-text: #333333;
}
[data-theme="blue"] {
--color-primary: #1E90FF;
--color-bg: #F8FAFC;
--color-text: #1E293B;
}
[data-theme="green"] {
--color-primary: #10B981;
--color-bg: #F0F9FF;
--color-text: #0C4A6E;
}
注意:不要只写 :root,必须配合 [data-theme="xxx"] 才能支持多套并存;data-theme 是手动控制的开关,不是系统级 prefers-color-scheme。
在 tailwind.config.js 中桥接变量
用 theme.extend.colors 把 CSS 变量映射进 Tailwind 类名系统:
module.exports = {
darkMode: 'class',
theme: {
extend: {
colors: {
primary: 'var(--color-primary)',
bg: 'var(--color-bg)',
text: 'var(--color-text)',
}
}
}
}
关键点:
- 必须用
extend,不能直接写colors: { ... },否则会覆盖掉gray-100、blue-500等所有内置颜色 - 变量名要和 CSS 中一致,比如
--color-bg对应bg: 'var(--color-bg)' - 透明度支持写成
bg: 'var(--color-bg) / 0.8',Tailwind 会自动解析
运行时切换主题并持久化
切换本质就是给 元素设置 data-theme 属性:
function setTheme(theme) {
document.documentElement.setAttribute('data-theme', theme)
localStorage.setItem('theme', theme)
}
// 初始化时读取
const sa ved = localStorage.getItem('theme') || 'blue'
setTheme(sa ved)
常见坑:
- 别用
document.body—— Tailwind 的darkMode: 'class'默认监听html元素 - 初始化时机要早,在 React/Vue 组件挂载前就得执行,否则首屏会闪动
- 如果用了
next-themes,它内部也是这么干的,但封装了系统偏好 fallback,适合 Light/Dark/System 三态场景
真正复杂的地方在于主题变量的粒度设计——比如按钮悬停色是否该随主题独立变化,还是复用主色微调;这些没法靠配置自动推导,得提前约定好变量命名和层级关系。
