「頁面換成暗色了,但輸入框還是白的、捲軸還是亮的、下拉選單打開是白底」——這一類 bug 的成因只有一個:沒有設 color-scheme。UA 繪製的元件不看你的 CSS 變數,它只看這個屬性。這一篇處理所有由瀏覽器而非你繪製的東西。
color-scheme 涵蓋的範圍
設定 color-scheme 後,UA 會自動處理:畫布底色、捲軸、表單控件(input、select、textarea、button 的預設外觀)、原生下拉清單、日期/顏色選擇器的彈出面板、拼字檢查底線、以及 autofill 的背景色。
這是一條非常划算的規則:一行宣告換掉一整批你根本無法用 CSS 完整控制的介面。 日期選擇器的彈出面板、<select> 展開的原生清單、autofill 的黃底,這些在多數瀏覽器上都無法用 CSS 完全覆寫,只能靠 color-scheme。
:root { color-scheme: light dark; }
:root[data-theme="light"] { color-scheme: light; }
:root[data-theme="dark"] { color-scheme: dark; }手動切換時務必連 color-scheme 一起改(見 三態切換器),否則會出現「頁面是暗的,但下拉選單是亮的」這種只有一半的畫面。
表單控件的細部調整
color-scheme 給的是合理預設,但品牌化仍需要幾個屬性:
:root {
accent-color: var(--accent); /* checkbox、radio、range、progress 的主色 */
caret-color: var(--text-primary); /* 輸入游標 */
}
input, textarea, select {
background: var(--bg-surface);
color: var(--text-primary);
border: 1px solid var(--border-subtle);
}
::placeholder { color: var(--text-muted); opacity: 1; } /* Firefox 預設會降透明度 */
::selection {
background: var(--accent);
color: var(--accent-text);
}accent-color 特別值得用:它讓核取方塊與滑桿套上品牌色,同時保留原生的可及性與觸控行為。瀏覽器會自動計算勾勾的對比色,所以你不需要另外處理暗色——但如果你的 accent 在暗色主題變成淺色,記得檢查勾勾是否仍清晰。
::placeholder 在 Firefox 有預設透明度,不寫 opacity: 1 會讓它在暗色下更淡、掉到 2:1 以下。
autofill 的背景色是老問題。Chromium 用 -webkit-autofill 偽類且無法用一般 background-color 覆寫(會被 UA 樣式蓋掉)。設好 color-scheme 之後多數情況已經正確;若仍需覆寫,通用的 hack 是用超長的 inset shadow:
input:-webkit-autofill {
-webkit-text-fill-color: var(--text-primary);
box-shadow: 0 0 0 1000px var(--bg-surface) inset;
transition: background-color 5000s; /* 拖延 UA 的背景動畫 */
}這是 hack,不是標準行為,升級瀏覽器時要重測。標示為瀏覽器相依。
捲軸
標準做法是 scrollbar-color 與 scrollbar-width:
:root {
scrollbar-color: var(--scrollbar-thumb) var(--scrollbar-track);
--scrollbar-thumb: light-dark(#c4c9d0, #3a3f47);
--scrollbar-track: light-dark(#f1f3f5, #17191d);
}三個注意事項:
- 多數情況下不要動它。 設了
color-scheme之後捲軸已經會跟著換色,而且是各平台原生的樣子。自訂捲軸經常做出比預設更難用的東西(太細、對比不足、失去 hover 放大)。 - 不要對捲軸顏色做動畫。 Chrome 的指引明確提到要避免,會造成明顯的效能問題。
- macOS 的覆蓋式捲軸(overlay scrollbar)在設定
scrollbar-color後可能被改成常駐佔位的樣式,導致版面寬度改變。要搭配scrollbar-width一起考量。
若仍需支援不認得 scrollbar-color 的舊瀏覽器,用 @supports not 包住 -webkit- 版本,避免兩套規則互相干擾:
@supports not (scrollbar-color: auto) {
::-webkit-scrollbar { width: 12px; }
::-webkit-scrollbar-track { background: var(--scrollbar-track); }
::-webkit-scrollbar-thumb { background: var(--scrollbar-thumb); border-radius: 6px; }
}forced-colors:與暗色模式無關的另一件事
Windows 高對比模式(現稱 Contrast Themes)不是暗色模式的一種,它是完全不同的機制:使用者選定一組有限的色盤,瀏覽器強制套用到所有網站,並透過 CSS 系統顏色關鍵字(Canvas、CanvasText、LinkText、ButtonFace、ButtonText、Highlight 等)把色盤提供給作者。
在這個模式下,你的 --bg-surface 幾乎全部被忽略。這是刻意的,也是正確的——使用者的對比需求優先於你的設計。你要做的不是對抗它,而是確保介面在被強制換色後仍然可用:
@media (forced-colors: active) {
/* 用背景色表達的狀態會消失,改用邊框 */
.badge { border: 1px solid CanvasText; }
/* 焦點環必須保留 */
:focus-visible { outline: 2px solid Highlight; outline-offset: 2px; }
/* 用 background-image 畫的圖示會不見,改用系統色 */
.icon { forced-color-adjust: none; color: CanvasText; }
}forced-color-adjust: none 是強力工具,MDN 的說明是它應該只用於支援使用者的顏色與對比需求(例如 UA 的優化反而讓體驗變差時),不是用來保住你的品牌色。過度使用等於把使用者明確表達的無障礙需求關掉。合理的用例是彩色的資料圖表、色票、品牌 logo——這些反而是把顏色抹掉會失去資訊的地方。
forced-colors 的檢查清單很短,但漏掉會直接導致不可用:
- 用純背景色區分的狀態(選取中的分頁、啟用的按鈕)在強制色下會全部一樣 → 加邊框或加文字標示。
- 用
background-image或 CSS 畫的圖示會消失 → 改 inline SVG(會被賦予系統色)或明確處理。 - 透明度與陰影會被移除 → 不要用它們承載唯一的資訊。
- 自訂的核取方塊(用
appearance: none重畫的)在強制色下經常變成看不見的方塊 → 這是把原生控件重畫的隱藏成本。
測試方式:Windows 上開「設定 → 協助工具 → 對比佈景主題」,或在 Chrome/Edge DevTools 的 Rendering 面板模擬 forced-colors: active。Firefox 也支援相關的模擬。
prefers-contrast:使用者想要更高對比
與 forced-colors 相鄰但不同的是 prefers-contrast,它表示使用者要求更高或更低的對比,但沒有強制色盤。可以用它把邊框加深、把 muted 文字拉亮:
@media (prefers-contrast: more) {
:root {
--text-muted: light-dark(#3d4550, #d0d6dd);
--border-subtle: light-dark(#9aa4b2, #6b747f);
}
}這是低成本、高回報的一段 CSS:幾行就能讓有需求的使用者拿到明顯更好的可讀性,而其他人完全不受影響。
下一步:Android 差異。