目标
在 Nuxt UI
项目中实现运行时动态切换主题颜色,让所有组件(包括 Nuxt UI 内置组件)都能响应主题变化,并且刷新页面后保持选择的主题,不出现闪烁。
挑战
想实现以上目标,有如下挑战:
Nuxt UI
的颜色系统是构建时确定的 -app.config.ts
中的primary
颜色在构建时生成静态样式- 运行时修改需要覆盖 CSS - 需要通过 CSS 变量来实现动态切换
- 页面刷新闪烁问题 - JavaScript 执行太晚,CSS 变量设置前页面已经开始渲染
解决方案
CSS层面
@import "tailwindcss";
@import "@nuxt/ui";
/* 设置默认的 primary 颜色变量(yellow 主题) */
:root {
--color-primary-50: oklch(98.7% 0.026 102.212);
...
--color-primary-950: oklch(28.8% 0.053 56.417);
}
@layer utilities {
/* 自定义 primary 颜色类 */
.bg-primary-50 { background-color: var(--color-primary-50) !important; }
...
.text-primary-50 { color: var(--color-primary-50) !important; }
...
.border-primary-50 { border-color: var(--color-primary-50) !important; }
...
}
/* 覆盖 Nuxt UI 的语义化 primary 颜色类 */
@layer components {
/* 背景色 */
.bg-primary { background-color: var(--color-primary-500) !important; }
.hover\:bg-primary\/75:hover { background-color: color-mix(in srgb, var(--color-primary-500) 75%, transparent) !important; }
.active\:bg-primary\/75:active { background-color: color-mix(in srgb, var(--color-primary-500) 75%, transparent) !important; }
.disabled\:bg-primary:disabled { background-color: var(--color-primary-500) !important; }
.aria-disabled\:bg-primary[aria-disabled="true"] { background-color: var(--color-primary-500) !important; }
/* 文字颜色 */
.text-primary { color: var(--color-primary-500) !important; }
.hover\:text-primary:hover { color: var(--color-primary-500) !important; }
/* 边框颜色 */
.border-primary { border-color: var(--color-primary-500) !important; }
.hover\:border-primary:hover { border-color: var(--color-primary-500) !important; }
/* 轮廓颜色 */
.outline-primary { outline-color: var(--color-primary-500) !important; }
.focus-visible\:outline-primary:focus-visible { outline-color: var(--color-primary-500) !important; }
/* Ring 颜色 */
.ring-primary { --tw-ring-color: var(--color-primary-500) !important; }
.focus-visible\:ring-primary:focus-visible { --tw-ring-color: var(--color-primary-500) !important; }
}
/* 强制覆盖 Nuxt UI 按钮颜色 - 最高优先级 */
button.bg-primary,
a.bg-primary,
.bg-primary {
background-color: var(--color-primary-500) !important;
}
button.hover\:bg-primary\/75:hover,
a.hover\:bg-primary\/75:hover,
.hover\:bg-primary\/75:hover {
background-color: color-mix(in srgb, var(--color-primary-500) 75%, transparent) !important;
}
button.active\:bg-primary\/75:active,
a.active\:bg-primary\/75:active,
.active\:bg-primary\/75:active {
background-color: color-mix(in srgb, var(--color-primary-500) 75%, transparent) !important;
}
.focus-visible\:outline-primary:focus-visible {
outline-color: var(--color-primary-500) !important;
}
.text-primary {
color: var(--color-primary-500) !important;
}
.border-primary {
border-color: var(--color-primary-500) !important;
}
作用:
- 提供默认颜色值,即使
JavaScript
未执行也有正确显示 - 使用
!important
覆盖Nuxt UI
的内置样式 - 让所有
bg-primary
和text-primary
等类引用 CSS 变量
Important
注意修改上面的…,替换为完整版本。
2 初始化脚本
// public/theme-init.js
(function() {
const savedTheme = localStorage.getItem('app-theme')
if (savedTheme) {
const themes = { /* 硬编码的主题数据 */ }
const theme = themes[savedTheme]
// 立即设置 CSS 变量
Object.keys(theme).forEach(function(key) {
document.documentElement.style.setProperty('--color-primary-' + key, theme[key])
})
}
})()
作用:
- 在 HTML
<head>
中同步执行,在任何样式渲染前就设置 CSS 变量 - 从 localStorage 读取用户上次选择的主题
- 避免页面刷新时的闪烁问题
3 nuxt配置
app: {
head: {
script: [
{ src: '/theme-init.js' }
]
}
}
作用:
- 将初始化脚本注入到每个页面的
<head>
中 - 确保脚本在页面加载早期执行
4 主题插件
export default defineNuxtPlugin({
name: 'theme',
setup() {
const appConfig = useAppConfig()
const savedTheme = localStorage.getItem('app-theme')
// 恢复保存的主题
if (savedTheme && savedTheme !== appConfig.ui.primary) {
appConfig.ui.primary = savedTheme
}
// 提供切换方法
const setTheme = (themeName) => {
appConfig.ui.primary = themeName
updateCSSVariables(themeName)
localStorage.setItem('app-theme', themeName)
}
return { provide: { setTheme } }
}
})
作用:
- 提供全局的
$setTheme()
方法 - 同步更新
app.config
和CSS
变量 - 持久化主题选择到 localStorage
5 主题配置
const themes = {
green: colors.green,
yellow: colors.yellow,
// ... 其他颜色
}
export default defineAppConfig({
themes,
ui: {
primary: 'yellow' as ThemeKey,
}
})
作用:
- 定义所有可用的主题颜色
- 设置默认主题
执行过程
页面首次加载
- HTML 开始解析
- theme-init.js 同步执行 → 设置 CSS 变量(如果有保存的主题)
- CSS 加载,使用 CSS 变量渲染页面
- Vue 应用初始化
- 主题插件执行 → 同步 app.config
- 页面完全渲染 ✅ 无闪烁
用户切换主题
- 用户点击按钮调用
$setTheme('blue')
- 更新 CSS 变量 → 页面立即变色
- 更新 app.config → Nuxt UI 组件响应
- 保存到 localStorage → 下次访问保持
页面刷新
- theme-init.js 从 localStorage 读取 'blue'
- 立即设置 blue 主题的 CSS 变量
- 页面以 blue 主题渲染 ✅ 无闪烁
- 插件恢复 app.config 状态
关键点
- CSS 变量 + !important:覆盖 Nuxt UI 的内置样式
- 同步脚本:在
<head>
中立即执行,避免异步延迟 - 硬编码主题数据:theme-init.js 中无法访问 Vue/Nuxt API,需要硬编码
- 三层防护:
- CSS 默认值(最基础)
- 初始化脚本(避免闪烁)
- Vue 插件(提供交互能力)