不做暗色模式的網站,在 2026 年不會停留在亮色,而是會被別人的演算法改成暗色。Chrome for Android 的 Auto Dark Theme 會對「沒有表態」的網站自動生成暗色版本,Android WebView 的 algorithmic darkening 在 app 端做同一件事,兩者的觸發條件都寫得很清楚:當網頁內容沒有使用 prefers-color-scheme、且作者沒有明確關閉時,UA 就接手。 所以真正的選項只有兩個——設計一份暗色主題,或明確宣告只支援亮色。沉默是最糟的第三種。

本專欄不談「暗色比較潮」。每一篇原子筆記處理一個可落地的元件:給完整可貼上的 CSS/JS、標明各自的必要條件與失效情境、並清楚區分穩定標準、瀏覽器相依行為與 hack。最重點的是 in-app WebView 那兩篇——那是雙主題最容易失控、也最少被正確處理的環境。

三個先決事實

一、prefers-color-schemecolor-scheme 是相反方向的兩件事。 前者是「瀏覽器告訴你使用者想要什麼」,後者是「你告訴瀏覽器這份文件支援什麼」。只寫前者的網站會得到白捲軸配深頁面、輸入框還是白底的半套畫面,因為 UA 從沒被告知它可以用暗色去畫那些原生元件。

二、In-app WebView 的 prefers-color-scheme 不是使用者的系統設定,是宿主 app 的佈景設定。 Android 官方文件寫得很明白:WebView 依 app 佈景的 isLightTheme 決定回報值;iOS 則來自 overrideUserInterfaceStyle。使用者手機是亮色、app 是暗色,你的頁面就會收到 dark。這不是你能修的,你只能讓兩套主題都完整可用。

三、閃爍問題只有一半需要 JS 解決。 系統偏好由 CSS 媒體查詢處理,不需要 JS 也不需要伺服器知道;真正需要首屏腳本補救的只有「使用者手動覆寫了系統偏好」這一種情況。搞清楚這個區分,就不會寫出一堆把系統偏好判斷塞進 JS 的錯誤實作。

能力階段

階段你已經能做到對應筆記
認識說得出 prefers-color-schemecolor-scheme 的方向差異、知道不宣告的代價01–02
會實作能用語意化 token 與 light-dark() 做出可維護的兩套主題,並處理 fallback03–04
會切換能做出三態切換器、選對儲存方式、首屏不閃爍05–06
會設計兩套主題都過 WCAG、暗色用明度而非陰影表達層次、圖片與圖示各用對的做法07–08
會收邊原生控件、捲軸、autofill、Windows 高對比模式都不破09
會上行動端theme-color、安全區域、Auto Dark Theme、iOS/Android 差異10
會處理 WebView理解 WebView 的機制、能診斷強制反色、有完整測試矩陣11–12
能排錯從症狀反推機制,三步定位問題13–14

「我想做到⋯⋯」索引

學習路徑

  1. 為什麼要支援雙主題:不宣告的代價不是「維持亮色」
  2. prefers-color-scheme 與 color-scheme:偵測偏好 vs 宣告能力
  3. 語意化顏色 token 設計:不要寫死 hex
  4. CSS 實作:變數切換、light-dark() 與 fallback
  5. dark
  6. 避免主題閃爍:FOUC 的真正成因與四種解法
  7. 對比、暗色不是純黑、高程與陰影
  8. 圖片、圖示、logo 與 media 的雙主題處理
  9. 原生控件、捲軸與 forced-colors
  10. Android 差異
  11. In-app WebView 專篇:WKWebView、Android WebView 與社群 App 內建瀏覽器
  12. 強制反色的偵測、對抗與測試矩陣
  13. 常見錯誤與排錯手冊
  14. 主張 vs 可佐證,與延伸資源

最小正確實作(可直接複製)

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <meta name="color-scheme" content="light dark">
  <script>
    // 只還原「手動覆寫」,系統偏好交給 CSS
    try {
      var t = localStorage.getItem('theme');
      if (t === 'light' || t === 'dark') {
        document.documentElement.dataset.theme = t;
        document.documentElement.style.colorScheme = t;
        document.querySelector('meta[name="color-scheme"]').content = t;
      }
    } catch (e) {}
  </script>
  <meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff">
  <meta name="theme-color" media="(prefers-color-scheme: dark)"  content="#0e1013">
  <meta name="theme-color" content="#ffffff">
  <link rel="stylesheet" href="/style.css">
</head>
:root {
  color-scheme: light dark;
  --bg-canvas:  light-dark(#ffffff, #0e1013);
  --bg-surface: light-dark(#f7f8fa, #14161a);
  --text:       light-dark(#16191d, #e6e8eb);
  --border:     light-dark(#e2e6ea, #262a30);
  --accent:      light-dark(#3b6fd4, #8fb3f0);
  --accent-text: light-dark(#ffffff, #0e1013);
  accent-color: var(--accent);
}
:root[data-theme="light"] { color-scheme: light; }
:root[data-theme="dark"]  { color-scheme: dark;  }
 
html, body { background: var(--bg-canvas); color: var(--text); }

這段涵蓋了:早期宣告(不閃爍)、原生控件跟著換色、手動覆寫可還原、行動端瀏覽器 UI 染色、以及讓 Android/Chrome 的自動反色不必出手。剩下的都是細節與收邊。

🔍 待解問題 / 持續追蹤

  • Facebook/Instagram/LINE/X 等 app 內建瀏覽器的具體版本行為未逐一實測。本專欄的相關結論是從 Android WebView 與 WKWebView 的官方機制推導,需要用診斷頁做實機驗證並建立記錄。
  • 桌面 Chrome 對 theme-color media 屬性的支援是否已從「僅限已安裝 PWA」擴大到一般分頁?引用資料為 2021 年前後,可能已變動。
  • light-dark() 何時實際進入 Baseline widely available(預估 2026-11 前後),屆時建構期降級是否還必要?
  • 哪些 in-app browser 使用非持久化資料存放區導致主題偏好無法保存?這會影響切換器的降級設計,但缺乏公開清單。
  • 主題切換用 View Transitions 的效能特性,尤其在低階 Android 裝置上的掉幀情形,尚未實測。
  • CSS 的 contrast-color() / color-contrast() 這類自動選前景色的功能成熟後,能否取代手工維護的 --accent-text 配對?

🕒 更新紀錄

  • 2026-08-14:初版,建立 hub + 14 篇原子筆記(基礎兩個關鍵字、token 設計、CSS 實作與 fallback、三態切換器、避免閃爍、視覺細節、圖片處理、原生控件與 forced-colors、mobile web、in-app WebView 專篇、強制反色與測試矩陣、排錯手冊、證據分級)。

此資料夾下有 14 條筆記。