深色模式最常見的錯誤,是把淺色介面直接反轉:白底變黑底、黑字變白字,收工。結果就是刺眼的純黑配純白、失去層次的扁平畫面,以及一批在暗背景下慘不忍睹的品牌色。深色模式不是換一層油漆,而是一套獨立的色彩與層級系統,得從對比、深度、語意 token 三個角度重新設計。今天把這件事拆開講清楚。

📖 學(核心)

別用純黑,也別用純白

把大面積背景設成 #000000 是新手最常犯的錯。純黑在 OLED 螢幕上會讓像素完全關閉,捲動時像素重新點亮有延遲,產生「拖影/塗抹」的視覺殘留;而純黑與純白之間對比過強,反而更容易造成眼睛疲勞。Google Material Design 建議用 #121212 這種近黑深灰當基準色,文字也用略帶灰的近白(而非死白 #FFFFFF),降低刺眼感的同時保留足夠可讀性。

這裡有個反直覺點:高對比對低視力者有幫助,但過度的亮暗反差會讓散光者看到文字邊緣「暈開」(halation,光暈效應),深色模式反而更難讀。所以目標不是把對比拉到最大,而是「舒適的對比」。

用亮度分層取代陰影

淺色介面靠陰影表現卡片浮起的深度,但在深色背景上陰影幾乎看不見——你沒辦法在 #000000 上投出影子。深色模式改用「越靠近使用者、表面越亮」的邏輯來表達層級:以 #121212 為底,elevation 第一層卡片約 #1E1E1E,第二層約 #242424,越上層越亮。搭配不同的色彩對比與不透明度堆疊,就能在沒有陰影的情況下做出清楚的空間感。純黑之所以糟,正是因為它讓所有元素都壓在同一個平面,毫無層次。

品牌色要去飽和

高飽和的鮮豔色在深色背景上會「振動」(vibration),邊緣看起來抖動、刺眼,長時間閱讀很累。正確做法是為深色模式準備一組去飽和、稍微調亮的品牌色變體,而不是沿用淺色模式那組。這也是為什麼深色主題該當成獨立設計來對待,而不是淺色的鏡像。

用語意 token 建立系統

維護得起的深色模式,核心是一套「語意化的 CSS 自訂屬性」。重點在於:token 命名要對應用途,而不是顏色本身。與其寫 --gray-900,不如定義 --color-bg-surface--color-text-primary--color-border,元件只消費這些語意 token。切換主題時只換 token 的值,元件程式碼一行都不用動。團隊還可以加 lint 規則,禁止在元件裡直接寫死色碼,只允許在 token 定義處出現原始色值。

三層架構與避免閃爍

一個完整的深色模式是三層工程問題:CSS 變數定義 token 系統、prefers-color-scheme 讀取系統偏好、localStorage 記住使用者的明確選擇,三層缺一不可。

最常被忽略的坑是 FOUC(未套用主題的閃爍)——慢網速下使用者先看到白色一閃、再淡入深色。解法是在 <body> 之後立刻放一段 inline script,在頁面繪製前就從 localStorage 讀取並套上主題。CSS 端可用 color-scheme: light dark; 搭配 :root[data-theme="dark"] 覆寫,並用新的 light-dark() 函式簡化雙主題色值宣告。一個實作良好的切換,在中階手機上樣式重算應在 2 毫秒內完成。

🧠 記

  • 背景用近黑 #121212,別用純黑;文字用近白,別用死白 #FFFFFF
  • 深色模式靠「表面越亮=越浮起」表現層級,不是靠陰影。
  • 品牌色要準備去飽和、調亮的深色變體,避免高飽和色振動刺眼。
  • 對比要「舒適」不是「最大」——過強對比會讓散光者看到光暈。
  • WCAG 底線:一般文字 4.5:1、大字 3:1,但深色模式建議做得比底線更寬裕。
  • 三層架構:CSS 變數 + prefers-color-scheme + localStorage
  • 用語意 token(--color-bg-surface)命名,不要用顏色命名(--gray-900)。
  • <body> 後放 inline script,在繪製前套主題,消除白色閃爍。

✍️ 實踐

  1. 打開你手上任一專案的深色模式,用取色器檢查背景色。如果是 #000000,先改成 #121212 感受差異。
  2. 建立一組語意 token:至少定義 --color-bg-base--color-bg-surface--color-text-primary--color-text-secondary--color-border 五個,並在 :root:root[data-theme="dark"] 各給一組值。
  3. 把一張卡片的深度改用「更亮的表面色」表現,而非 box-shadow,對比純黑底和 #121212 底下的層次感差別。
  4. 用 WebAIM Contrast Checker 或瀏覽器 DevTools 檢查深色模式下所有文字對比,確認達到 4.5:1;把不合格的近白文字微調到合格。
  5. 加上防閃爍的 inline script:在 <body> 後讀 localStorage 的主題設定並套上 data-theme,重新整理頁面確認不再白閃。

🔗 延伸學習


💬 問 AI

我正在為{產品/專案名稱}設計深色模式,目前的做法是{現況,例如:直接反轉淺色配色}。
請幫我:
1. 檢視我的背景色 {色碼} 與主要文字色 {色碼},指出對比與舒適度問題;
2. 提出一組語意化的 CSS token 命名與深/淺兩套色值建議;
3. 說明如何用亮度分層取代陰影來表現 {某個元件,例如:卡片/彈窗} 的層級;
4. 給我一段避免主題閃爍(FOUC)的 inline script 範例。