Theme / v2.15.0

UI UX Pro Max

把模糊需求變成可實作、可驗收的介面

實戰範例

實戰範例 003:用 Design Tokens 建立主題切換器

逐步設計預設、品牌與深色主題,理解 Token 映射、預覽、持久化與首次載入閃爍的處理方式。

用 Design Tokens 建立主題切換器

專案目標

同一個後台要提供預設亮色、品牌主題和深色模式。使用者可先預覽,按下儲存後才套用到後續頁面;元件不應為每個主題寫一套 CSS。

1. 分離 raw 與 semantic token

:root {
  --indigo-600: 79 70 229;
  --slate-900: 15 23 42;
  --white: 255 255 255;

  --surface-page: 248 250 252;
  --surface-card: var(--white);
  --text-primary: var(--slate-900);
  --action-primary: var(--indigo-600);
}

元件只使用 --surface-card--action-primary。主題資料只需改變 semantic token 的映射。

2. 定義主題

const themes = {
  default: {
    surfacePage: '248 250 252',
    surfaceCard: '255 255 255',
    textPrimary: '15 23 42',
    actionPrimary: '79 70 229',
  },
  dark: {
    surfacePage: '15 23 42',
    surfaceCard: '30 41 59',
    textPrimary: '241 245 249',
    actionPrimary: '129 140 248',
  },
} as const;

套用時由一個邊界函式負責寫入 CSS 變數:

function applyTheme(theme) {
  const root = document.documentElement;
  root.dataset.theme = theme;
  for (const [name, value] of Object.entries(themes[theme])) {
    root.style.setProperty(`--${name.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`)}`, value);
  }
}

3. 預覽與儲存

預覽只修改目前 DOM;儲存才寫入 localStorage。初始化時要處理 SSR 沒有 window、儲存空間被封鎖和無效主題名稱。

const stored = localStorage.getItem('theme');
const initial = stored === 'dark' ? 'dark' : 'default';
applyTheme(initial);

為避免頁面先亮後暗,正式產品可在 HTML head 的極小 inline script 先讀取偏好;這段程式必須保持簡單,並提供預設值。

4. 驗證主題品質

不要只檢查背景和按鈕。逐一檢查文字、placeholder、邊框、focus ring、錯誤、成功、disabled、圖表和圖片,因為它們通常仍然綁著亮色主題的假設。

驗收

  • 預覽、取消和儲存的行為彼此清楚。
  • 重新整理後能恢復已儲存的主題。
  • 兩種主題都能完成主要任務。
  • 元件沒有直接使用主題專屬色票。

延伸練習

加入跟隨系統偏好的選項,並設計「系統、亮色、深色」三者互相切換時的優先順序。