小程序不支持动态修改:root CSS变量,主题切换必须用预设class+条件绑定,主题状态需本地持久化并同步更新导航栏和tabbar样式。

小程序端不支持动态修改 :root CSS 变量
微信/支付宝等小程序基础库根本不暴露 document.documentElement 的写入能力,document.documentElement.style.setProperty('--theme-color', 'red') 在小程序里直接静默失败,连报错都没有。这不是 bug,是平台限制——小程序样式系统是编译时注入的,运行时无法 patch 根节点的 CSS 自定义属性。
所以别在小程序里尝试用 CSS 变量做主题切换主力。真正能生效的方式只有一种:预设 class + 条件绑定。
- 所有主题样式必须写成
.theme-dark .button、.theme-light .card这种嵌套形式,不能依赖:root下的变量计算 - 根容器(比如
<view class="app-root">)必须用:class动态绑定主题 class,例如['app-root', themeClass] -
themeClass必须是 computed 属性,且值只能是字符串'theme-light'或'theme-dark',不能是对象或布尔值 - 小程序里改
document.body.className无效,必须作用在页面级<view>上,且该 view 是整个页面的最外层容器
主题状态必须持久化并提前注入 App.vue
用户点一次“换肤”,下次打开小程序还是默认色?那是因为你没在点击那一刻就存进本地——uni.setStorageSync('theme_mode', 'dark') 必须在按钮事件回调里立刻执行,不能等到 onLaunch 再写。
更关键的是:App.vue 的 onLaunch 和 onShow 都得读取存储并更新 themeClass。否则页面比 App.vue 先渲染,会闪一下默认主题。
- 存的时候 key 统一用
'theme_mode',值限定为'light'/'dark'/'auto',别用true/false,小程序对布尔转字符串行为不一致 -
onLaunch里读一次,初始化data.theme;onShow里再读一次,应对后台切回前台时主题被其他页面改过的情况 - 不要把主题逻辑塞进 Vuex 或
Vue.prototype,小程序里响应式监听失效率高,且Vue.prototype在条件编译下容易漏初始化
导航栏和 tabbar 颜色必须单独调用 API 同步
只改页面 class 不会影响原生导航栏和 tabbar——它们是小程序原生组件,CSS 完全不生效。必须显式调用对应 API:
- 导航栏背景色:
uni.setNavigationBarColor({ backgroundColor: '#2c3e50' }) - tabbar 样式:
uni.setTabBarStyle({ backgroundColor: '#34495e' }) - tabbar 选中图标颜色:
uni.setTabBarItem({ index: 0, color: '#3498db' }) - 这些 API 调用必须和 class 切换同步执行,不能靠 watch 监听主题变量——App 端和小程序端的 watch 都不可靠
样式工程建议:用 SCSS @mixin 避免重复写 selector
手写几十个 .theme-dark .button、.theme-dark .input 很容易漏,也难维护。推荐在 uni.scss 里定义主题混入:
@mixin themeify {
@each $theme-name, $theme-map in $themes {
.theme-#{$theme-name} &,
&.theme-#{$theme-name} {
$theme: $theme-map !global;
@content;
}
}
}
然后在组件里复用:
.button {
@include themeify {
background-color: map-get($theme, bg);
color: map-get($theme, text);
}
}
这样既保持样式隔离,又避免硬编码主题名,后期加 theme-ocean 只需往 $themes 对象里塞新配置,不用改任何组件样式。
最易被忽略的一点:小程序里主题 class 必须从 App.vue 根节点开始透传,子页面不能靠全局变量“猜”当前主题——因为页面可能被缓存,onLoad 不触发,themeClass 就永远停留在旧值。所有页面的根 <view> 都得显式绑定 :class="getThemeClass()",而这个函数必须每次读 uni.getStorageSync('theme_mode')。


















