闪屏问题本质是“先亮后暗”:必须在HTML源码中写死data-bs-theme属性,服务端根据cookie或session注入,JS切换后需调用Theme.update(),并声明color-scheme: light dark。

闪屏问题本质是“先亮后暗”:浏览器首帧渲染时,document.documentElement还没被 JS 修改,CSS 变量按默认值计算,页面先以亮色呈现,JS 执行完再跳变。哪怕延迟几十毫秒,人眼就能感知。必须在 HTML 源码中直接写死 data-bs-theme 属性,而不是等 JS 加载后才设。
HTML 标签里必须写死 data-bs-theme
服务端或模板中直接输出带属性的 标签,值取决于用户上次选择(从 cookie 或服务端 session 读取),不是固定写 dark 或 light:
<html lang="zh-CN" data-bs-theme="light">
常见错误是留空或默认不写,让 JS 全权控制——这必然导致首屏亮色闪动。Next.js/Nuxt 等 SSR 框架需在服务端根据请求头或 cookie 判断初始主题,注入到 HTML 字符串中。千万别用 document.body.setAttribute('data-bs-theme', ...),这个属性对 :root 变量完全无效。
切换后必须调用 bootstrap.Theme.getOrCreateInstance().update()
只改 data-bs-theme 属性,Bootstrap 的 CSS 自定义变量(如 --bs-body-bg)不会自动重算。漏掉这步,就是“点了没反应”的最常见原因。
立即学习“前端免费学习笔记(深入)”;
- UMD 环境(如
bootstrap.bundle.min.js)需额外引入bootstrap/js/dist/theme.js,再调用bootstrap.Theme.getOrCreateInstance(document.documentElement).update() - ESM 环境需先
import { Theme } from 'bootstrap',再执行new Theme(document.documentElement).update() - SSR 场景下,此调用必须放在
useEffect或DOMContentLoaded后,且 DOM 已挂载
别混用 @media (prefers-color-scheme: dark) 和手动切换逻辑
媒体查询只是快照,不响应运行时变化;而用户点击“切暗色”是主动行为。两者信号源不同,硬凑一起必然状态错乱。
- 用
window.matchMedia('(prefers-color-scheme: dark)').matches仅作首次加载兜底,不监听后续变化 - 所有运行时切换,统一走
document.documentElement.setAttribute('data-bs-theme', ...)+.update() - CSS 规则全部基于
html[data-bs-theme="dark"]编写,不依赖媒体查询类名(如.dark) - 第三方库(如 React-Toastify)若自带主题检测,关掉它的自动模式,由你控制
data-bs-theme状态
记得声明 color-scheme: light dark
这个声明不控制你的 CSS,但它决定原生控件的渲染逻辑:<input> 边框、<select></select> 下拉箭头、滚动条、canvas 默认边框等,在暗色系统下仍显示亮色,就是缺了它。
必须显式声明::root { color-scheme: light dark; },否则即使页面主题正确,原生控件也会逃逸出暗色体系。


















