ARTICLE · 1144094
uni-app 暗黑模式与主题切换:CSS 变量三档方案
一、先把三档的交互定下来
主题切换不是「开/关」两档,成熟的产品都是三档:跟随系统、浅色、深色。默认跟随系统,用户手动选了就用手动的。这个决策直接影响实现方式:要能同时拿到「系统当前是什么」和「用户选了什么」,再合成最终值。

二、CSS 变量打底:颜色只定义两遍
整套方案的地基是把颜色收敛成语义化 CSS 变量,页面里绝不写死色值:
/* uni.scss 或 App.vue 全局样式 */page { --bg-page: #f5f6f7; --bg-card: #ffffff; --text-primary: #333333; --text-secondary: #666666; --brand: #09BB07;}/* 深色档:根节点换变量值,页面代码零改动 */.page-dark { --bg-page: #111111; --bg-card: #1e1e1e; --text-primary: #e8e8e8; --text-secondary: #999999; --brand: #07c160;}.card { background: var(--bg-card); color: var(--text-primary);}
深色模式不是简单反色,变量值要重新调:深色底上的「次要文字」不能直接用深色底上的主色反推,对比度要按 WCAG 标准过一遍,纯黑背景换带一点灰的 #111,视觉舒适度差很多。
三、切换机制:三端各有各的监听
// utils/theme.js —— 合成最终主题const themeStore = useThemeStore() // 持久化的用户选择function resolveTheme() { if (themeStore.choice !== 'auto') return themeStore.choice // auto 档:读系统状态 const sys = uni.getAppBaseInfo().theme || 'light' return sys}// 应用到页面根节点:H5 绑在根元素 class,小程序换 page-metafunction applyTheme(theme) { themeStore.current = theme}
<!-- 页面里用 page-meta 改页面根节点背景,三端通用 --><page-meta :page-style="`background-color: var(--bg-page)`" />
系统状态变化的监听,小程序和 App 用 uni.onThemeChange,H5 用 matchMedia('(prefers-color-scheme: dark)') 加 change 监听。三端监听方式不同,但回调里做的事一样:用户在 auto 档时重算主题并更新变量。
四、别漏掉的角落:图片和第三方组件
•图片:写死白底的 PNG 在深色下像补丁。图标用 SVG 或字体图标跟 CSS 变量换色;必要的小插画准备两套,按主题切换 src
•第三方组件库:选型时就要确认暗黑支持程度,后面组件库选型那篇会专门对比。不支持的就给组件套一层变量覆盖
•导航栏和 tabbar:原生导航栏的颜色不走 CSS 变量,主题切换时要同步调 uni.setNavigationBarColor和 uni.setTabBarStyle
这篇的方案可以概括成三层:CSS 变量把颜色收敛成语义 token,深浅两套值只定义两遍;用户选择持久化到 Pinia store,auto 档按端监听系统变化重算;最后把图片、三方组件、原生导航栏这些「变量管不到的角落」逐个适配掉。三端行为对齐之后,暗黑模式就从「敢不敢上」变成了「按清单扫一遍」的事。
觉得有用的话,点赞、分享、推荐三连支持一下。