Theme / v2.15.0

UI UX Pro Max

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

實戰範例

實戰範例 009:第一次為新專案建立配色與主題

從一句品牌描述開始,透過 palette-picker 與 theme-exporter 走完語意色彩角色、對比度檢查、深色主題到 CSS 變數交付的完整流程。

第一次為新專案建立配色與主題

專案目標

你剛接手一個內部客服後台,品牌端只給了一句「我們的品牌色是青色 (teal),整體要穩重」。你要把這句話變成可套用、可換膚、可通過 WCAG AA 對比度檢查的 CSS 變數,而且不能讓 AI 自己腦補十個顏色。

本範例假設你已完成 CLI 安裝並初始化,能在 AI 助理裡呼叫 palette-pickertheme-exporter 兩個 skill。

npm install -g ui-ux-pro-max-cli
uipro init --ai universal

1. 從產品情境出發,不要從色票

先把需求寫成一段結構化的輸入。palette-picker 不接受「藍色科技感」這種形容詞,它需要角色與限制。

產品情境:客服後台,使用者一整天盯著看
品牌色:teal #0d9488
主要操作:儲存、送出
狀態種類:成功、警告、錯誤、資訊
主題:亮色 + 深色
內容密度:高(表格、表單、明細)
不可妥協:正文文字對比度 ≥ 4.5:1

把「一整天盯著看」寫進去很重要。它會讓飽和度被壓低,避免高彩度背景長時間造成眼睛疲勞。

2. 用 palette-picker 展開語意角色

把上面這段輸入交給 palette-picker,它輸出的是語意角色,而不是十六支 hex 色。

:root {
  /* raw 色票,元件不直接使用 */
  --teal-500: 13 148 136;
  --teal-600: 13 148 120;
  --slate-900: 15 23 42;
  --slate-700: 51 65 85;

  /* 語意角色,元件只認這層 */
  --surface-page: 248 250 252;
  --surface-card: 255 255 255;
  --text-primary: var(--slate-900);
  --text-secondary: var(--slate-700);
  --action-primary: var(--teal-600);
  --action-primary-hover: var(--teal-500);
  --border-subtle: 226 232 240;
  --state-success: 22 163 74;
  --state-warning: 217 119 6;
  --state-error: 220 38 38;
}

注意 --action-primary 用 600 而不是 500。在白底上,teal-600 對比度更接近 4.5:1,而 500 用在表格次要文字上會踩到 AA 下限。

3. 檢查對比度,而不是目測

每個文字色和背景色都要算。把結果寫進 design notes,之後審查才看得出哪個角色沒過。

配對                              對比度    結論
text-primary / surface-card      16.8:1    AAA
text-secondary / surface-card     7.4:1    AAA
action-primary / surface-card     4.6:1    AA(一般文字)
action-primary / surface-page     4.2:1    不過,改用於大字或圖示
state-warning / surface-card      4.0:1    不過,限大字或圖示

action-primary 在 page 背景上不過 4.5:1,所以按鈕的背景要用 card 色,不要直接放在 page 上。這就是「語意角色」的價值:它逼你在邊界處做選擇。

4. 為深色主題重新挑值,不要反轉

深色不是把每個變數反相。palette-picker 會另外輸出一組深色角色,通常需要降低飽和度並提高文字明度。

[data-theme='dark'] {
  --surface-page: 15 23 42;
  --surface-card: 30 41 59;
  --text-primary: 241 245 249;
  --text-secondary: 203 213 225;
  --action-primary: 45 212 191;
  --action-primary-hover: 94 234 212;
  --border-subtle: 51 65 85;
  --state-success: 74 222 128;
  --state-warning: 251 191 36;
  --state-error: 248 113 113;
}

深色模式下狀態色通常要提高明度,因為暗背景會吃掉彩度。重新算一次對比度,不要沿用亮色的結論。

5. 用 theme-exporter 交付 CSS 變數

角色通過驗證後,把整份 token 交給 theme-exporter。它會把 raw 與語意層分開輸出,並提醒哪些元件直接讀了 raw 色票(這是違規)。

// theme-exporter 產生的 entry,元件只 import 這份
export const tokens = {
  light: {
    surfacePage: '248 250 252',
    surfaceCard: '255 255 255',
    textPrimary: '15 23 42',
    actionPrimary: '13 148 120',
  },
  dark: {
    surfacePage: '15 23 42',
    surfaceCard: '30 41 59',
    textPrimary: '241 245 249',
    actionPrimary: '45 212 191',
  },
} as const;

交付前確認三件事:元件沒有 hard-code --teal-500、Tailwind 的 theme.extend.colors 是合併而不是覆蓋、首次載入沒有亮暗閃爍。

驗收

  • 亮色與深色主題都通過 AA(正文 4.5:1,大字 3:1)。
  • 元件只讀語意角色,在專案內搜尋不到 raw hex。
  • 按鈕在 page 背景上仍可讀(沒有踩到 4.2:1 那條)。
  • 切換主題不會出現首屏閃爍。

關鍵學習點

  • 從產品情境輸入,而不是色形容詞,palette-picker 才能選出可解釋的角色。
  • 元件只認語意 token,raw 色票只給 theme-exporter 用,這是日後換膚不爆炸的前提。
  • 對比度要逐對計算並記錄,深色主題必須重算,不能沿用亮色結論。
  • 交付前檢查元件有沒有偷接 raw 色,Tailwind 設定是合併不是覆蓋。