Tauri v2 窗口边框主题联动
> 适用范围:让原生窗口装饰(标题栏、边框)颜色跟随应用的深色/浅色主题自动切换。
原理
Tauri v2 提供 getCurrentWindow().setTheme('dark' | 'light') API,底层映射到各平台的原生接口:
| 平台 | 底层实现 | 生效范围 |
|---|---|---|
| macOS | NSAppearance |
标题栏、窗口边框 |
| Windows 10/11 | DwmSetWindowAttribute(DWMWA_USE_IMMERSIVE_DARK_MODE) |
标题栏颜色 |
| Linux | GTK 主题切换 | 窗口装饰 |
> Windows 需要 Build 1809(2018年10月更新)或更高版本才支持深色标题栏。
实现步骤
第一步:授权 Capability
在 src-tauri/capabilities/default.json 中添加权限。
Tauri v2 的 Capability 系统对每个窗口 API 都需要显式声明,缺少此条权限时 setTheme() 的 Promise 会被静默拒绝,不报任何前端错误。
{
"permissions": [
"core:default",
"core:window:allow-set-theme"
]
}
第二步:调用 setTheme API
在主题切换逻辑中,随 CSS 主题同步调用 Tauri 窗口 API。
Vue 示例(composable 模式):
import { getCurrentWindow } from '@tauri-apps/api/window'
// watchEffect 回调是同步函数,必须用 .catch() 而非 try/catch 捕获 Promise 错误
watchEffect(() => {
const isDark = theme.value === 'dark'
// 1. 切换 CSS 主题(data-theme 属性或 class)
document.documentElement.setAttribute('data-theme', theme.value)
// 2. 同步原生窗口边框颜色
getCurrentWindow()
.setTheme(theme.value) // 'dark' | 'light'
.catch((e) => console.error('Failed to set native window theme:', e))
})
React/其他框架示例:
import { getCurrentWindow } from '@tauri-apps/api/window'
function applyTheme(isDark: boolean) {
const theme = isDark ? 'dark' : 'light'
document.documentElement.setAttribute('data-theme', theme)
getCurrentWindow()
.setTheme(theme)
.catch((e) => console.error('Failed to set native window theme:', e))
}
注意事项
⚠️ 异步调用的陷阱
setTheme() 返回 Promise<void>,必须用 .catch() 处理错误:
// ✅ 正确:.catch() 能捕获 Promise rejection
getCurrentWindow().setTheme('dark').catch(console.error)
// ❌ 错误:try/catch 在同步回调中无法捕获异步错误
try {
getCurrentWindow().setTheme('dark') // Promise 被丢弃,错误被吞掉
} catch (e) { ... }
⚠️ decorations 必须开启
setTheme 只影响原生窗口装饰(标题栏),无边框窗口(decorations: false)上无效果。
// src-tauri/tauri.conf.json
{
"app": {
"windows": [
{
"decorations": true
}
]
}
}
⚠️ 初始化时机
应在主题状态读取后立即调用,避免闪烁:
// 从 localStorage 同步读取,不用等待异步
const saved = localStorage.getItem('app-theme')
const theme = ref<'dark' | 'light'>(saved === 'light' ? 'light' : 'dark')
// watchEffect 带 immediate 效果,模块加载时立即执行一次
watchEffect(() => {
getCurrentWindow().setTheme(theme.value).catch(console.error)
})
平台兼容性
| 平台 | 支持 | 备注 |
|---|---|---|
| macOS 10.14+ | ✅ | 完整支持 |
| Windows 11 | ✅ | 完整支持 |
| Windows 10 Build 1809+ | ✅ | 支持,效果略有差异 |
| Windows 10 更早版本 | ❌ | 不支持,标题栏保持系统默认色 |
| Linux (GNOME/KDE) | ⚠️ | 取决于 GTK 主题配置 |
快速自查清单
集成时按下表逐项核对:
- [ ]
capabilities/default.json已声明core:window:allow-set-theme(缺失时 Promise 会被静默拒绝) - [ ]
tauri.conf.json中窗口decorations: true - [ ] 调用处使用
.catch()处理Promise<void>,未用同步try/catch包裹 - [ ] 主题状态从
localStorage等同步读取,首帧立即setTheme,避免闪烁 - [ ] CSS 主题与
setTheme在同一处一起切换(如watchEffect/applyTheme) - [ ] 确认目标平台在支持范围内(macOS 10.14+ / Win10 1809+;Linux 视 GTK 而定)