Skip to content
返回设计中心

暗色模式实现模式

基于令牌的主题、闪烁预防与系统偏好检测,构建稳健暗色模式。

组件模式进阶

为什么

用户期望暗色模式 — 30%+ 系统级启用。差的实现加载时白闪、忽略 OS 偏好或硬编码颜色破坏品牌主题。令牌驱动无闪烁水合是基本要求。

何时使用

任何面向消费者的应用、文档站或夜间使用工具都应实现暗色模式。纯打印或固定照明的嵌入式自助终端 UI 跳过。

如何做

1. 定义令牌对 — --bg-light / --bg-dark,绝不组件特定暗色覆盖 2. 设置 CSS — :root { --bg: #fff } :root[data-theme="dark"] { --bg: #0f172a } 3. 内联脚本 — 首次绘制前读 localStorage + matchMedia 设置 data-theme 4. 切换组件 — 日/月图标,更新属性 + localStorage,派发事件 5. 系统同步 — matchMedia("(prefers-color-scheme: dark)") 监听器,用户选 "system" 时更新 6. 图片 — 通过 <picture> 或 CSS filter 提供暗色优化变体 7. 测试 — Storybook 中切换,Playwright CI 截图两主题

示例

<!-- index.html — <body> 前内联脚本防闪烁 --> <head> <script> (function () { var t = localStorage.getItem("theme") || "system"; var dark = t === "dark" || (t === "system" && matchMedia("(prefers-color-scheme: dark)").matches); document.documentElement.dataset.theme = dark ? "dark" : "light"; })(); </script> </head> /* tokens.css — 成对令牌,组件仅引用 --bg */ :root { --bg: #ffffff; --text: #0f172a; --surface: #f1f5f9; } :root[data-theme="dark"] { --bg: #0f172a; --text: #f1f5f9; --surface: #1e293b; } body { background: var(--bg); color: var(--text); } // ThemeToggle.tsx — 更新属性 + 存储 + 通知 function ThemeToggle() { const [theme, setTheme] = useState(localStorage.getItem("theme") || "system"); const apply = (t: string) => { localStorage.setItem("theme", t); document.documentElement.dataset.theme = t === "dark" || (t === "system" && matchMedia("(prefers-color-scheme: dark)").matches) ? "dark" : "light"; setTheme(t); }; return <button onClick={() => apply(theme === "dark" ? "light" : "dark")}> {theme === "dark" ? "☀" : "☾"} </button>; }

常见问题

  • ⚠FOUC(无样式内容闪烁)— 主题在水合后应用,刷新时白闪
  • ⚠硬编码暗色 — .dark .btn { background: #1e293b } 而非令牌,破坏白标
  • ⚠遗忘表面 — bg 令牌换了但模态/输入仍是浅表面,看起来坏了
  • ⚠Logo 不可见 — 黑 logo 在暗背景上,未提供 <picture> 变体

最佳实践

  • ✓在 <head> CSS 前内联主题脚本 — 零闪烁,不依赖水合
  • ✓用 3 模式:light / dark / system — 多数应用强制二选一忽略 OS 偏好
  • ✓审计暗色下每个表面 — 用 Storybook dark addon,非手动切换
  • ✓通过 <picture><source media='(prefers-color-scheme: dark)'> 提供暗色优化 logo

常见问题解答

如何防止页面加载白闪?

在 <head> CSS 前内联脚本读 localStorage 和 matchMedia,再在 <html> 设 data-theme。首次绘制前应用主题,无闪烁。

应支持 light/dark 还是也支持“system”?

三模式:light、dark、system。“system”跟随 prefers-color-scheme 并随 OS 变化更新。多数应用强制二选一忽略 OS 偏好,用户不喜欢。

为什么暗色下 logo 不可见?

黑 logo 在暗背景消失。通过 <picture><source media='(prefers-color-scheme: dark)'> 提供暗色变体,或用 CSS filter 反相。

如何主题化而不每组件硬编码暗色?

定义成对令牌(--bg、--text、--surface)在 :root[data-theme='dark'] 覆盖。组件仅引用 var(--bg)——绝不组件特定暗色覆盖。

如何审计暗色下每个表面?

用 Storybook dark-mode addon 切换所有 stories,Playwright CI 截图两主题。别手动点——会漏模态和输入。