主题系统

主题由 swift.config.json 的 theme.mode 单旋钮驱动,一个字段同时决定三件事:加载哪套 token、声明什么 color-scheme、是否提供切换器。

theme.mode 四选一

// nuxt.config.ts
swiftConfig: {
  theme: {
    mode: 'toggle',      // 'system' | 'toggle' | 'dark' | 'light'
    default: 'auto',     // 仅 toggle:切换器初始态 'auto' | 'light' | 'dark',默认 'auto'
    toggleStates: 3,     // 仅 toggle:3=三态(可回自适应)/ 2=两态(仅明暗),默认 3
  }
}
mode加载的 tokencolor-scheme切换器典型场景
'system'浅 + 暗暗色语境下 dark(跟随系统)❌ 不提供想跟随系统、但不给用户手动切
'toggle'浅 + 暗暗色语境下 dark✅ useTheme() + SwThemeSwitch用户可切明暗并记忆
'dark'固定深色dark❌固定深色站(调色板由 applier overrides 提供)
'light'(starter 默认)固定浅色light❌固定浅色站,公司多数项目

theme 只接受对象形态。固定单主题站直接写 { mode: 'light' } 或 { mode: 'dark' }。

每种 mode 都会让原生 UI(滚动条、下拉、日期选择器等)跟对,避免「深色页面配浅色滚动条」这类割裂,但声明方式按 mode 分两类:固定 dark / light 在 :root 上常驻声明;system / toggle 只在暗色语境(@media (prefers-color-scheme: dark) 与 [data-theme='dark'])声明 color-scheme: dark,浅色语境不声明、走浏览器默认(等效浅色)。

工作原理

toggle 模式下,框架在 <html> 上设置 data-theme="light|dark",CSS 用属性选择器覆盖变量。框架还会自动注入一段 head 内联 bootstrap script:首帧渲染前同步读取 localStorage 中的用户偏好并抢先设置 data-theme,避免「手选主题与系统偏好相反时整页加载闪一帧错色」(FOUC)——无需业务配置。变量覆盖示例:

:root {
  --background: #ffffff;
  --foreground: #1a1a1a;
}

[data-theme='dark'] {
  --background: #0f0f12;
  --foreground: #e5e7eb;
}

auto(跟随系统)用 prefers-color-scheme 媒体查询检测系统偏好,并在系统偏好变化时自动切换。固定 dark / light 模式则由框架注入对应的 color-scheme 声明、不挂 data-theme 切换逻辑。

useTheme() 使用

<script setup lang="ts">
const { theme, resolvedTheme, mode, toggleTheme, setTheme } = useTheme()
</script>

<template>
  <p>当前主题:{{ theme }}(实际明暗:{{ resolvedTheme }})</p>

  <!-- 仅 toggle 模式下 toggleTheme / setTheme 生效;其它模式为 no-op -->
  <button @click="toggleTheme()">切换主题</button>
  <button @click="setTheme('dark')">深色</button>
  <button @click="setTheme('light')">浅色</button>
  <button @click="setTheme('auto')">跟随系统</button>
</template>
  • theme 是用户选择的状态('auto' | 'light' | 'dark'),resolvedTheme 是当前实际明暗('light' | 'dark',auto 态取系统偏好)——切 logo / 插画按 resolvedTheme 判断。
  • toggleTheme() / setTheme() 仅在 mode: 'toggle' 时生效,其它模式为 no-op(主题由 mode 锁定)。

详细 API 参见 useTheme() composable 文档。

内置切换器 SwThemeSwitch

不想自己写切换按钮时,直接放框架内置的 SwThemeSwitch——它由 useTheme() 驱动、自 gate:仅 mode: 'toggle' 时渲染按钮,其它模式渲染为空。改 theme.mode 配置,切换器自动出现 / 消失,无需改模板。

<template>
  <!-- 默认内联 SVG 图标(sun / moon / auto 半圆);mode≠toggle 时自动不显示 -->
  <SwThemeSwitch />
  <SwThemeSwitch :size="22" />

  <!-- 框架不绑图标库:用具名 slot 传入自己图标库的图标 -->
  <SwThemeSwitch>
    <template #light><PhSun :size="18" /></template>
    <template #dark><PhMoon :size="18" /></template>
    <template #auto><PhCircleHalf :size="18" /></template>
  </SwThemeSwitch>
</template>
  • size prop:默认内联图标尺寸(px,默认 18;用 slot 传自定义图标时不生效)。
  • slot #light / #dark / #auto:覆盖对应态图标,作用域提供 { theme, resolved }(#auto 仅三态出现)。
  • 皮肤走 --sw-theme-switch-* token(尺寸 / 圆角 / hover 背景等),applier 可覆盖。

持久化

用户选择存在 localStorage 的 theme key,刷新后自动恢复。为在首屏 inline script 阶段同步读取避免 FOUC,useTheme() 用裸 localStorage(不走加密缓存 util)——这是框架认可的例外,业务做主题切换时与它共用同一 key 即可。

自定义主题变量

/* 在 assets/styles/global.css 中覆盖框架默认变量 */
:root {
  --primary: #6366f1;
}

[data-theme='dark'] {
  --primary: #818cf8;
}

固定 dark 模式下,直接在 :root 定义深色调色板即可(无需 [data-theme] 选择器,因为不切换)。