vike-vue-content

通过 Vike config、defineTheme() 和 useTheme() 管理主题,并在首屏前应用 CSS 变量。

主题系统

主题系统的目标很简单:把主题编译成稳定的 CSS 变量契约,并在首屏和运行时都应用到 document.documentElement

当前数据流:

  1. +config.ts 里通过 themethemesappearance 提供默认主题。
  2. vike-vue-content/config 自动注册 headHtmlBegin,在首屏前注入一段初始化脚本。
  3. 初始化脚本优先读取 localStorage['vvc-theme'];如果没有用户覆盖,则回退到 Vike config 默认值。
  4. useTheme()ThemeSettings 在客户端更新同一份状态,并把结果写回 localStorage
  5. 主题 token 最终会展开成 --color-*--font-*--space-*--radius

这套设计是 client-authoritative:默认值来自 Vike config,用户修改后由客户端持久化覆盖。

快速开始

框架内置两个主题组件:

  • ThemeToggle:切换 light / dark / system
  • ThemeSettings:完整主题面板,支持 Primary、Neutral、Radius、Font 和导出
<script setup>
import { ThemeToggle } from 'vike-vue-content/components/theme-toggle'
import { ThemeSettings } from 'vike-vue-content/components/theme-settings'
</script>

<template>
  <header>
    <h1>My App</h1>
    <div>
      <ThemeSettings />
      <ThemeToggle />
    </div>
  </header>
</template>

记得在布局里引入:

import 'vike-vue-content/index.css'

index.css 仍然需要,它提供组件样式和主题相关的基础样式。真正的 CSS 变量值不是静态写死在 CSS 里的,而是由首屏初始化脚本和 useTheme() 在运行时设置。

如果你使用 ThemeSettings,要注意它和 +config.ts 的关系:

  • +config.ts 里的 themethemesappearance 决定站点默认主题
  • ThemeSettings 只是在客户端覆写这份默认值
  • 覆写结果存放在 localStorage['vvc-theme']
  • 点击重置按钮后,会丢弃这层覆写,回退到 +config.ts 默认配置

在 Vike 中配置默认主题

import type { Config } from 'vike/types'
import vikeVue from 'vike-vue/config'
import vikeVueContent from 'vike-vue-content/config'
import { defineTheme } from 'vike-vue-content/theme'

const brand = defineTheme({
  name: 'brand',
  fonts: {
    sans: "'Open Sans', sans-serif"
  },
  radius: '0.5rem',
  light: {
    primary: '#8b5cf6',
    neutral: 'slate'
  },
  dark: {
    primary: '#8b5cf6',
    neutral: 'slate'
  }
})

export default {
  extends: [vikeVue, vikeVueContent],
  theme: 'brand',
  themes: [brand],
  appearance: 'system'
} satisfies Config

几个字段的含义:

  • theme:当前启用的主题名
  • themes:注册可用主题对象
  • appearance:默认外观,取值为 'light' | 'dark' | 'system'

如果你什么都不配,会回退到内置默认主题:blue + slate + Inter + 0.25rem

defineTheme()

defineTheme() 接收的是 token 对象,返回规范化后的主题对象。

import { defineTheme } from 'vike-vue-content/theme'

const brand = defineTheme({
  name: 'brand',
  fonts: {
    sans: "'Outfit', sans-serif"
  },
  radius: '0.375rem',
  spacing: {
    container: '1.5rem'
  },
  light: {
    primary: '#0f766e',
    neutral: 'slate',
    bg: '#fcfffe'
  },
  dark: {
    primary: '#0f766e',
    neutral: 'slate',
    bg: '#081311'
  }
})

规范如下:

  • light / dark 下的字段会变成 --color-*
  • fonts 下的字段会变成 --font-*
  • spacing 下的字段会变成 --space-*
  • radius 会变成 --radius

其中有两个特殊 token:

  • primary

    • 支持命名色板,也支持 raw hex,例如 '#0066cc'
    • raw hex 会通过 Chroma.js 自动生成 primary-lightprimary-dark
  • neutral

    • 当前只支持命名中性色板
    • 会自动展开出 mutedbgsurfacetextborder 等语义变量

如果浅色和深色共用一套颜色,也可以写 colors 作为两者的共同回退值。

内置预设

Primary 预设:

black red orange amber yellow lime green emerald teal cyan sky blue indigo violet purple fuchsia pink rose

Neutral 预设:

slate gray zinc neutral stone

Radius 预设:

0 0.125 0.25 0.375 0.5

Font 预设:

Inter system-ui Roboto Open Sans Montserrat Poppins Outfit Raleway

编程式控制

useTheme() 会读取当前默认主题,应用到 DOM,并把用户修改持久化到 localStorage

<script setup lang="ts">
import { useTheme } from 'vike-vue-content/composables/theme'

const {
  state,
  theme,
  isDark,
  primary,
  neutral,
  radius,
  font,
  mode,
  modes,
  toggleDarkMode,
  resetTheme,
  exportCSS,
  exportConfig,
  exportVikeThemeConfig
} = useTheme()
</script>

常用写法:

primary.value = 'violet'
primary.value = '#0066cc'
neutral.value = 'zinc'
radius.value = 0.5
font.value = 'Outfit'
mode.value = 'dark'

说明:

  • mode 对应的是 appearance
  • toggleDarkMode() 会按 light -> dark -> system 循环
  • resetTheme() 会清掉用户覆盖,回到 Vike config 默认值
  • exportVikeThemeConfig() 导出的是可直接放回 themes 的规范化 JSON

如果你只想操作持久化状态,不需要 DOM 应用逻辑,可以用 useThemeStorage()

CSS 变量契约

主题系统对外暴露的是一组稳定变量:

变量含义
--color-primary主色
--color-primary-light主色浅阶
--color-primary-dark主色深阶
--color-muted中性基准色
--color-muted-light中性浅阶
--color-muted-dark中性深阶
--color-bg页面背景
--color-surface表面背景
--color-surface-elevated浮层 / hover 背景
--color-text主文本
--color-text-muted次级文本
--color-text-dimmed弱化文本
--color-border边框
--color-border-muted柔和边框
--font-sans主字体
--radius圆角
--space-*自定义间距 token

暗色模式激活时,根节点会带上 .dark 类,但通常你不需要手写 .dark 覆盖,直接消费变量就够了:

<style scoped>
.card {
  background: var(--color-surface);
  color: var(--color-text);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
}

.card:hover {
  background: var(--color-surface-elevated);
}

.link {
  color: var(--color-primary);
}
</style>

导出与低层 API

ThemeSettings 面板里有两个导出按钮:

  • main.css:导出当前主题对应的 CSS
  • vike-theme.json:导出可直接回填到 themes 的主题对象

如果你想在代码里自行处理,也可以直接使用 @vike-vue-content/theme 暴露的低层 API:

import {
  exportThemeCss,
  exportVikeThemeConfig,
  themeToVars,
  themeToCss,
  themeToAppearanceCss
} from 'vike-vue-content/theme'

适用场景:

  • 生成静态 CSS 文件
  • 把主题对象落盘到 JSON
  • 自定义主题编辑器
  • 在非 Vue 场景里复用同一套 token 编译逻辑