主题与明暗模式
插件如何跟随宿主的明暗主题、主题色与窗口透明度,三步接入与底层机制。
设计令牌的两套值(亮 / 暗)靠 html 上的 class 切换。这一章讲清楚切换机制,以及插件接入主题联动的标准做法。
切换机制
体系支持三条通道,优先级从高到低:
| 通道 | 触发方式 | 效果 |
|---|---|---|
| 强制暗色 | html.dark | 应用整套暗色变量,color-scheme: dark |
| 强制亮色 | html.light | 应用亮色变量,color-scheme: light |
| 跟随系统 | 无 class | 按系统 prefers-color-scheme 自动切换 |
color-scheme 同时让原生控件(<select> 弹出面板、滚动条、表单底色)跟随主题,这是免费获得的一致性。
宿主设置里选「跟随系统 / 浅色 / 深色」时,会向所有 WebView 广播 update-theme-info 事件;主题色(六预设 + 自定义)与窗口透明度也走同一事件。
三步接入(工程型插件)
第一步,入口引入全局样式(设计令牌都在里面):
import '@udhold/udhold-ui/styles/global.css'
第二步,入口调用一次 setupThemeSync():
import { setupThemeSync } from '@udhold/udhold-ui/utils'
setupThemeSync() // 拉取宿主当前主题 + 监听后续变更,宿主不可用时静默降级
它做三件事:向宿主查询当前 ThemeInfo 立即应用;监听 update-theme-info 事件实时跟随;每次变更同步三样东西——html.dark/light class(明暗)、body.theme-* class 或内联 --primary-color(主题色)、--window-opacity / --panel-opacity(透明度)。
第三步,组件里需要感知明暗做逻辑分支时(比如给代码编辑器换主题),用 useColorScheme:
<script setup lang="ts">
import { computed } from 'vue'
import { useColorScheme } from '@udhold/udhold-ui/composables'
const { isDark } = useColorScheme()
const editorTheme = computed(() => (isDark.value ? 'vs-dark' : 'vs'))
</script>
isDark 是响应式的:宿主切换主题的瞬间自动更新。样式层面则不需要它——令牌本身就是双份的,跟着 class 自动切换。
免构建插件的做法
没有 npm 工程的静态插件拿不到 setupThemeSync,用宿主桥直接暴露的 API 等价实现:
<script>
// 初始快照:webview 创建时宿主注入
const boot = window.__UDHOLD_BOOT__ || {}
apply(boot.theme || {})
// 跟随变更
window.udhold.onThemeChange((theme) => apply(theme))
function apply(info) {
const dark = info.theme === 'dark'
|| (info.theme !== 'light' && matchMedia('(prefers-color-scheme: dark)').matches)
document.documentElement.classList.toggle('dark', dark)
document.documentElement.classList.toggle('light', !dark)
}
</script>
页面样式则按双通道写:
body { background: #f1f2f3; color: #1f2124; }
html.dark body { background: #1c1d1f; color: #f2f3f4; }
主题色的正确用法
- 永远用
var(--primary-color)表达主色,不要抄某个预设的色值——用户切到紫色主题时,写死的蓝色会立刻变得刺眼。 - 选中、高亮、品牌氛围用派生令牌
--primary-light-bg(10% 底)与--primary-hover(hover 加深)。 - 红 / 绿 / 黄等结果语义用
--danger-color/--success-color/--warning-color,与主色区分开:主色表达「这是谁」,语义色表达「发生了什么」。
检查清单
上线前过一遍:
- 入口调用了
setupThemeSync(),宿主切明暗时插件实时跟随 - 样式里没有写死的颜色(搜一遍
#开头的色值) - 图片图标用
currentColor或自适应反色(见图标体系与 AdaptiveIcon) - 暗色下检查过对比度:次级文字
--ink-2在--surface上仍可读 - 原生控件(下拉、日期选择)没有突兀地保持亮色(确认没有显式设置
color-scheme)
接下来看基础样式类:按钮、输入框、开关这些高频控件,引完样式文件就能直接写 class 用。