Skip to content
返回构建教程

主题桥接

用 CSS 变量和主题提供方把设计系统主题(亮/暗/品牌)桥接到代码。

为什么

暗模式不再可选——用户期望它,OS 级偏好使它对许多人成默认。主题桥用 CSS 自定义属性(变量)让同组件在任何主题正确渲染,通过换一组变量值而非条件类。这比每元素加 'dark:' 类可维护得多:组件不知主题,只用 var(--surface-bg)。桥还处理品牌主题化(白标 SaaS),同应用服务多品牌不同调色板——用 CSS 变量时这是配置变更非代码变更。

何时使用

需 ≥2 主题(亮/暗或多品牌)时。约 2-3 小时。单主题内部工具可跳过。

如何做

1. 在 :root 定义语义 token 为 CSS 自定义属性
   - --surface-bg、--surface-fg、--border-color、--accent-primary
   - 这些是组件唯一引用的颜色——永不原始 hex

2. 在 [data-theme="dark"] 或 .dark 选择器定义主题覆盖
   - .dark { --surface-bg: #0a0a0a; --surface-fg: #fafafa; }
   - 每主题只是一组变量覆盖,无组件变更

3. 加 ThemeProvider 在 <html> 设 data-theme 并持久化 localStorage
   - 首访读 prefers-color-scheme;让用户通过开关覆盖
   - 首次绘制前应用主题(<head> 内联脚本)避免 FOUC

4. Tailwind 里把 CSS 变量映射到工具类
   - colors: { surface: { bg: "var(--surface-bg)", fg: "var(--surface-fg)" } }
   - 则 bg-surface-bg 在所有主题自动工作

5. 多品牌:运行时从配置(DB 或 JSON)加载品牌 token
   - 从 JS 在 :root 设 CSS 变量——document.documentElement.style.setProperty('--accent-primary', brand.color)

示例

/* globals.css — 语义 token + 暗覆盖 */
:root {
  --surface-bg: #ffffff; --surface-fg: #0a0a0a; --accent-primary: #6366f1;
}
.dark {
  --surface-bg: #0a0a0a; --surface-fg: #fafafa; --accent-primary: #818cf8;
}

/* tailwind.config.ts */
export default { theme: { extend: { colors: {
  surface: { bg: "var(--surface-bg)", fg: "var(--surface-fg)" },
  accent: { primary: "var(--accent-primary)" },
} } } };

// ThemeProvider(无 FOUC——<head> 内联脚本)
export function ThemeProvider({ children }) {
  useEffect(() => {
    const saved = localStorage.getItem("theme") || "system";
    const theme = saved === "system" ? matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light" : saved;
    document.documentElement.classList.toggle("dark", theme === "dark");
  }, []);
  return <>{children}</>;
}

常见问题

  • ⚠每元素用 Tailwind dark: 变体而非 CSS 变量,加第三主题要碰每组件。
  • ⚠加载时无样式内容闪(FOUC)因主题在 React effect 而非内联脚本应用。
  • ⚠首访不尊重 OS 偏好,暗模式用户在 effect 跑前得白闪。
  • ⚠组件硬编码品牌色而非变量,白标要全 fork 代码库。

最佳实践

  • ✓用语义 token 名(surface-bg 非 white)让 token 含义在主题变更中存活。
  • ✓React 水合前用 <head> 内联脚本应用主题——完全消除 FOUC。
  • ✓Storybook 里每屏在亮和暗模式测——视觉回归抓对比度问题。
  • ✓多品牌服务端加载品牌 token 并内联 HTML,无默认品牌闪。