用 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、圖表和圖片,因為它們通常仍然綁著亮色主題的假設。
驗收
- 預覽、取消和儲存的行為彼此清楚。
- 重新整理後能恢復已儲存的主題。
- 兩種主題都能完成主要任務。
- 元件沒有直接使用主題專屬色票。
延伸練習
加入跟隨系統偏好的選項,並設計「系統、亮色、深色」三者互相切換時的優先順序。