行動裝置上的雙主題比桌機多三件事要處理:瀏覽器 UI 的顏色、安全區域與橡皮筋回彈露出的底色、以及系統層級的自動反色。三者都不是版面問題,而是「你的頁面之外那一圈」的問題,而使用者感受到的破綻恰恰在那裡。
theme-color:染色瀏覽器 UI
<meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff">
<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0e1013">支援狀況需要分開講,因為差異很大:
- Safari 15 起(macOS 與 iOS)支援
theme-color,且是第一個支援media屬性搭配prefers-color-scheme的桌面瀏覽器。iOS Safari 會用它染上下工具列。 - Android Chrome、Brave、Samsung Internet 支援
theme-color,用來染網址列。 - 桌面 Chrome 的支援限於已安裝的 PWA(自 v93 起)。一般分頁瀏覽不會套用。
規則是:若定義多個 theme-color,瀏覽器採用第一個匹配的。所以順序有意義,而且應該再加一個沒有 media 的 fallback放在最後,給不懂 media 屬性的瀏覽器:
<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">手動覆寫時,media 版本不會跟著變(它只認系統偏好),所以切換器要主動更新那個無 media 的 tag,或直接改寫全部:
function syncThemeColor(resolved) {
const color = resolved === 'dark' ? '#0e1013' : '#ffffff';
document.querySelectorAll('meta[name="theme-color"]')
.forEach(m => m.setAttribute('content', color));
}theme-color 的顏色應該等於頁面最上緣的背景色,不是品牌色。若頁首是白的而 theme-color 是品牌藍,會出現一條與內容無關的色帶。
橡皮筋回彈與安全區域:那條白邊哪來的
iOS Safari 過度捲動時會露出頁面背後的底色,而那個底色來自 html 元素,不是 body。只在 body 設背景色的網站,在暗色主題下捲到頂或底時會露出白色(或 UA 畫布色),造成「中間黑、上下白」的夾心。
html { background-color: var(--bg-canvas); } /* 這行是必要的 */
body { background-color: var(--bg-canvas); }同樣的道理適用於瀏海與底部 home indicator 區域。若用了 viewport-fit=cover,要記得那塊區域的顏色也由 html 決定:
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">body {
padding-top: env(safe-area-inset-top);
padding-bottom: env(safe-area-inset-bottom);
}固定在底部的操作列在暗色主題特別容易出問題——它的背景要延伸到安全區域之外,但內容要留在安全區域內:
.bottom-bar {
background: var(--bg-surface);
padding-bottom: calc(12px + env(safe-area-inset-bottom));
}PWA / standalone 模式
加到主畫面之後,狀態列的樣式由另一個 meta 決定:
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">值只有 default、black、black-translucent 三種,沒有辦法依 prefers-color-scheme 切換。black-translucent 讓內容延伸到狀態列下方、狀態列文字為白色,配合 viewport-fit=cover 在暗色主題下效果最好,但在亮色主題下白字會看不見。
若產品同時要支援兩種主題的 standalone 模式,實務上的解法是選 default 並讓系統決定狀態列文字顏色,或接受在其中一個主題下的妥協。這是目前的平台限制,不是可以繞過的東西。
Web App Manifest 的 theme_color 與 background_color 同樣是單值,沒有雙主題機制。background_color 影響啟動畫面(splash screen)的底色——選一個在兩套主題下都不會太突兀的中間值,或依產品主要使用情境選擇。
Android Chrome 的 Auto Dark Theme
這是 Android 上最重要的一件事。當使用者在 OS 開啟暗色,且在 Chrome 設定裡開啟「盡可能對網站套用深色主題」時,Chrome 會對亮色網站自動生成一份暗色版本。
觸發條件的關鍵是:網站沒有自己支援暗色。只要你正確宣告了 color-scheme: light dark 並提供了暗色樣式,Auto Dark Theme 就不會介入。這是最好的處理方式——不是關掉它,而是讓它沒有必要出手。
若確定要退出(顏色即資料的介面),用 only light:
<meta name="color-scheme" content="only light">:root { color-scheme: only light; }
.color-picker { color-scheme: only light; } /* 也可以只退出局部 */Chrome 官方的建議很明確:寧可做一份自己策劃的暗色主題,也不要直接退出,因為退出等於放棄使用者在電池、無障礙上的收益。
測試方法:
- DevTools:More Tools → Rendering → 勾選 Emulate auto dark mode。
- 實機:
chrome://flags啟用#darken-websites-checkbox-in-theme-setting,再到「設定 → 主題」勾選深色網站選項。
iOS 與 Android 的行為差異整理
| 項目 | iOS Safari | Android Chrome |
|---|---|---|
prefers-color-scheme | 跟隨系統外觀(含自動日夜) | 跟隨系統 |
| 自動反色 | 沒有內建的網站自動反色 | 有(Auto Dark Theme),可用 only light 退出 |
theme-color | 支援,含 media 屬性 | 支援,media 支援情況較零散 |
| 過捲背景 | 露出 html 背景,非常明顯 | 一般為 overscroll glow,較不明顯 |
| 系統字級 | Safari 有逐站點縮放;Dynamic Type 主要影響 app | 協助工具的字體大小會縮放網頁文字 |
「iOS 沒有網站自動反色」這一點值得記住,它意味著iOS 上如果網站不支援暗色,使用者就是拿到亮色——所以 iOS 使用者對「網站沒做暗色」的感受更直接,Android 使用者則更容易遇到「被做壞的暗色」。兩邊的抱怨內容不同,但根因是同一個。
系統字級與可讀性
暗色主題下,細字重在深底上會顯得更細(光暈使筆畫看起來被吃掉)。實務上的補償是暗色主題的正文字重加一階、或字距稍微放寬:
@media (prefers-color-scheme: dark) {
body { -webkit-font-smoothing: antialiased; }
}-webkit-font-smoothing: antialiased 在 macOS 上會讓文字變細,這在亮色主題是常見的做法,但在暗色主題會讓細字更難讀。不要無條件全站套用,這是需要實際比對後決定的參數。
系統字級放大是另一件必須一起處理的事:用 rem 而非 px 定義字級與間距,不要用 text-size-adjust: none 關掉 Android 的文字放大,並確保版面在字級放大 200% 時不會破。這與主題無關,但會在暗色主題下被放大檢視——因為暗色使用者當中,因視覺需求而選擇暗色的比例更高。
下一步:In-app WebView 專篇。