StoneRen

在nuxt ui中进行theme设置

Oct 03, 2025

目标

Nuxt UI 项目中实现运行时动态切换主题颜色,让所有组件(包括 Nuxt UI 内置组件)都能响应主题变化,并且刷新页面后保持选择的主题,不出现闪烁。

挑战

想实现以上目标,有如下挑战:

  1. Nuxt UI 的颜色系统是构建时确定的 - app.config.ts 中的 primary 颜色在构建时生成静态样式
  2. 运行时修改需要覆盖 CSS - 需要通过 CSS 变量来实现动态切换
  3. 页面刷新闪烁问题 - 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-primarytext-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.configCSS 变量
  • 持久化主题选择到 localStorage

5 主题配置

const themes = {
  green: colors.green,
  yellow: colors.yellow,
  // ... 其他颜色
}

export default defineAppConfig({
  themes,
  ui: {
    primary: 'yellow' as ThemeKey,
  }
})

作用:

  • 定义所有可用的主题颜色
  • 设置默认主题

执行过程

页面首次加载

  1. HTML 开始解析
  2. theme-init.js 同步执行 → 设置 CSS 变量(如果有保存的主题)
  3. CSS 加载,使用 CSS 变量渲染页面
  4. Vue 应用初始化
  5. 主题插件执行 → 同步 app.config
  6. 页面完全渲染 ✅ 无闪烁

用户切换主题

  1. 用户点击按钮调用 $setTheme('blue')
  2. 更新 CSS 变量 → 页面立即变色
  3. 更新 app.config → Nuxt UI 组件响应
  4. 保存到 localStorage → 下次访问保持

页面刷新

  1. theme-init.js 从 localStorage 读取 'blue'
  2. 立即设置 blue 主题的 CSS 变量
  3. 页面以 blue 主题渲染 ✅ 无闪烁
  4. 插件恢复 app.config 状态

关键点

  1. CSS 变量 + !important:覆盖 Nuxt UI 的内置样式
  2. 同步脚本:在 <head> 中立即执行,避免异步延迟
  3. 硬编码主题数据:theme-init.js 中无法访问 Vue/Nuxt API,需要硬编码
  4. 三层防护:
    1. CSS 默认值(最基础)
    2. 初始化脚本(避免闪烁)
    3. Vue 插件(提供交互能力)
0-PRESENT © StoneRenver:2510031922