Skip to content

useTheme ​

主题(明暗)运行时切换 composable。模块级单例——全应用共享同一份主题状态,自动持久化到 localStorage 并写入 document.documentElement 的 data-theme,驱动 [data-theme='dark'] 令牌翻转。

适用场景 ​

场景说明
全站明暗切换顶栏放一个切换按钮,调 toggleTheme() 即驱动整站 --ai-chat-* 令牌翻转
跟随系统 + 记忆选择resolvedTheme 联动 prefers-color-scheme,用户显式选择持久化到 localStorage
SSR 安全的主题注入配合文档站 head 内联脚本做首屏防闪(本站即此方案)
组件内隔离主题target: 'component' 时写入组件根子树,不影响宿主 <html>

典型消费组件:无直接消费(主题经 CSS 变量与 data-theme 生效,全组件隐式受益)。参考消费方:playground 的 PlaygroundDemo、主题配置器 ThemeBuilder。

代码演示 ​

切换模式观察整站 --ai-chat-* 令牌翻转与 resolvedTheme 解析(system 随系统明暗):

theme: system · resolvedTheme: light

函数签名 ​

typescript
function useTheme(options?: UseThemeOptions): {
  theme: Ref<ThemeMode>
  resolvedTheme: Readonly<Ref<ResolvedTheme>>
  setTheme: (t: ThemeMode) => void
  toggleTheme: () => void
}

模块同时导出单例计算属性 resolvedTheme(非组件场景直接读取当前生效明暗)与 componentTheme(target: 'component' 时的组件根 data-theme 绑定值,html 模式下为 undefined)。

类型 ​

typescript
export type ThemeMode = 'light' | 'dark' | 'system'
export type ResolvedTheme = 'light' | 'dark'

export interface UseThemeOptions {
  /** data-theme 写入位置:'html'(宿主根,默认)或 'component'(组件根元素) */
  target?: 'html' | 'component'
  /** 是否读写 localStorage,默认 true */
  persist?: boolean
}

选项 ​

选项类型默认值说明
target'html' | 'component''html'data-theme 写入位置。'html' 写宿主 <html>,整页生效(默认,向后兼容);'component' 只写组件根元素,暗色令牌仅作用于组件子树,不碰宿主主题系统
persistbooleantrue是否把主题读写到 localStorage['ai-chat-theme']。false 关闭持久化,刷新后回到默认

选项由首次带 options 的调用生效(单例),后续调用忽略;不带 options 的调用保持默认行为。

返回值 ​

属性 / 方法类型说明
themeRef<ThemeMode>当前主题模式,'system' 表示跟随系统
resolvedThemeReadonly<Ref<ResolvedTheme>>实际生效的明暗(system 已解析为 light / dark)
setTheme(t)(t: ThemeMode) => void设置主题并持久化
toggleTheme()() => void在 light / dark 之间切换

特性 ​

  • 模块级单例:任意多个组件调用 useTheme() 共享同一份状态,切换一处全局生效。
  • 持久化:默认写入 localStorage['ai-chat-theme'],刷新后保留;persist: false 关闭。
  • 跟随系统:theme = 'system' 时监听 prefers-color-scheme,系统切换自动跟随。
  • 自动应用:把解析后的明暗写入 data-theme 属性,组件令牌自动翻转;写入位置由 target 选项决定(默认 <html>)。
  • SSR 安全:所有 window / localStorage 访问都有 typeof window 守卫,可在 <script setup> 中直接调用。

基础用法 ​

vue
<script setup lang="ts">
import { useTheme } from '@toimc/vue'

const { theme, resolvedTheme, setTheme, toggleTheme } = useTheme()

// 跟随系统
setTheme('system')
// 或强制亮 / 暗
setTheme('dark')
// 或一键切换
toggleTheme()
</script>

<template>
  <button @click="toggleTheme">
    {{ resolvedTheme === 'dark' ? '🌙' : '☀' }}
  </button>
</template>

组件内主题(不碰宿主) ​

宿主应用有自己的主题系统时,用 target: 'component' 把 data-theme 限定在组件子树,同时可用 persist: false 关闭 localStorage 写入:

vue
<script setup lang="ts">
import { useTheme } from '@toimc/vue'

const { resolvedTheme, toggleTheme } = useTheme({ target: 'component', persist: false })
</script>

<template>
  <button @click="toggleTheme">
    {{ resolvedTheme === 'dark' ? '🌙' : '☀' }}
  </button>
</template>

此模式下宿主 <html> 不会被写入 data-theme,暗色令牌只在聊天组件子树内翻转。

相关 ​