深色模式切換

深色模式切換鈕可讓使用者覆寫單一網站的系統色彩配置。更新 <meta name="color-scheme"> 即可套用覆寫:light dark 表示「遵循系統偏好設定」,light 或 dark 則會固定明確的配置。顏色 (light-dark())、系統顏色和原生 UI 都會根據 CSS color-scheme 屬性進行調整,因此切換鈕會讓該屬性與 <meta> 保持同步 (請參閱「在 CSS 中反映覆寫」)。

持續顯示切換鈕、設定控制項,還是完全不顯示切換鈕?

大多數網站不需要在網站 UI (標頭、導覽、頁尾等) 中提供永久色彩配置切換鈕。根據預設,請遵循系統偏好設定,如果需要手動覆寫,請將控制項放在獨立的設定介面:設定頁面、面板或疊加層。

將配置固定在 OS 上是合理但罕見的意圖,而罕見的意圖應放在漸進式揭露機制後方,而不是放在隨時可見的控制項中。

永久切換鈕大多出現在開發人員或與開發人員相關 (例如設計師) 的網站上,因為這些網站切換配置的頻率遠高於一般網頁使用者。 請勿僅因為這是開發人員網站上的常見模式而新增:開發人員對此控制項的興趣不代表一般使用者。

導入作業

首先,請按照「深色模式」中的所有步驟操作,支援這兩種配置,並預設為系統偏好設定。

資料模型

深色模式切換鈕的資料模型通常包含三種狀態:

  1. 系統 (未儲存值):使用者介面會採用系統色彩配置。理想情況下,這應該是預設值。
  2. 淺色 (儲存為 "light"):無論系統色彩配置為何,UI 都會使用淺色主題。
  3. 深色 (儲存為 "dark"):無論系統色彩配置為何,UI 都會使用深色主題。

這個狀態通常會儲存在 localStorage 中,並反映在文件標頭的 <meta name="color-scheme"> 元素中。

在 CSS 中反映覆寫

<meta> 只是呈現提示:作者 CSS 中的任何 color-scheme 宣告都會覆寫該提示。由於深色模式會在 :root 上設定 color-scheme: light dark,因此單獨更新 <meta> 是無作業,CSS 會勝出。將 <meta> 對應回屬性,讓串聯遵循該屬性:

:root {
  color-scheme: light dark;

  &:has(> head > meta[name="color-scheme"][content="dark"]) {
    color-scheme: dark;
  }
  &:has(> head > meta[name="color-scheme"][content="light"]) {
    color-scheme: light;
  }
}

請勿將 color-scheme: light 或 color-scheme: dark 硬式編碼為根預設值;基本宣告必須保持 light dark。CSS 不得依賴 JS:如果 JS 從未執行,<meta> 會維持 light dark,網站會遵循系統偏好設定,不會發生任何問題。

持續性和 FOUC 預防

將覆寫設定保留在 localStorage 中;返回系統預設值時,請移除該項目。

為避免錯誤配置閃爍,請在 <meta> 元素後方,立即使用內嵌指令碼 (非 type=module,非 defer) 套用儲存的值:

<meta name="color-scheme" content="light dark">
<script>
document.querySelector('meta[name="color-scheme"]').content = localStorage.getItem("color-scheme") ?? "light dark";
</script>
  • 這個內嵌指令碼僅用於防止 FOUC。請盡可能縮小這個檔案,用於處理色彩配置切換的其他 JS 則可稍後載入。
  • 在極少數情況下,localStorage 存取權可能會擲回 (例如網站資料遭到封鎖),但由於這個指令碼不會執行其他動作,因此唯一的副作用是控制台錯誤。如果這點很重要,請用 try .. catch 包裝。即使主題無法保留,仍要確保主題已套用至頁面。
  • 覆寫值可能會從其他分頁變更:請處理 window 的 storage 事件,保持同步。
  • 當系統偏好設定變更為與儲存值相符時,請勿使用 matchMedia() 移除儲存值。許多使用者的 OS 會根據時間自動切換配置;如果每次兩者剛好一致時就移除儲存的值,就完全無法固定配置。

HTML 和非顏色值的分支

顏色不需要額外處理:<meta> 對應至 color-scheme (如上) 後,light-dark() 會遵循覆寫。本節說明如何調整其他值 (例如 font-weight、媒體來源等)。

如果網頁只是遵循系統偏好設定,則可使用 (prefers-color-scheme: dark) 進行分支,包括在 <picture> 或 <video> 來源中。覆寫色彩配置會增加額外的複雜度:系統偏好設定不再是可靠資料來源,而且對於只接受媒體查詢的 API,沒有直接替代方案。

請勿完全依賴 JS 套用的類別 (例如 .dark) 進行分支,因為如果 JS 未載入且 OS 預設為深色,這些類別就會不正確。

您可以將媒體查詢與 :root:has(> head > meta[name="color-scheme"][content="dark"]) (或 light) 選取器結合,建立分支。如要瞭解如何妥善導入這項功能,請參閱「選取器和 @ 規則組合」。

控制項本身的選項也是非顏色分支:在標記中加入兩種狀態 (圖示加上視覺上隱藏的動作文字,例如「切換至深色主題」),並透過這些選取器隱藏非使用中的狀態。display: none 也會從輔助技術中移除多餘的選項,因此當系統偏好設定變更時,控制項不需要 JS 就能保持最新狀態。

如果是 HTML 內嵌媒體,目前唯一的方法是將兩個版本都納入為獨立元素,並適當切換顯示設定。使用 display: none 隱藏 <source> 元素無效。

使用者介面

兩或三個州?

在所有情況下,UI 都必須支援還原或將色彩配置設為系統。

在空間有限的切換鈕中,建議顯示兩種狀態,例如網站 Chrome (標頭、導覽、頁尾等) 中的永久切換鈕,這類切換鈕的空間有限,且調整設定通常與使用者目標無關。

在獨立設定介面 (頁面、面板或疊加層) 中,色彩配置控制項是不同的情境,可能會明確顯示所有三種狀態 (「淺色」、「深色」、「系統」):使用者已對未來行為做出審慎決定,且有空間說明選項。

如果網站採用情境感知配置 (例如 OS 處於深色模式時,採用較暗的淺色模式),則也應提供三個明確狀態,因為「淺色」和「系統 (目前為淺色)」確實不同。

雙態切換鈕

使用這類控制項時,通常是為了暫時調整舒適度 (「現在太亮了」),而不是為了確保目前狀態能長期維持。因此,對於雙態切換,只要顯示三個設定中的兩個即可:系統 (以便還原設定) 和系統目前的相反值 (儲存為目前的常值)。

只有兩種狀態:

  1. 系統預設:沒有儲存的值,<meta>內容light dark。顯示為目前的解析值 (例如:淺色主題時顯示太陽圖示)。
  2. 覆寫:儲存為 light 或 dark。

基本上,這是一種三態控制項 (light dark、light、dark),其中與目前系統偏好設定相符的明確狀態無法連線。

使用者切換設定時:

  1. 目標配置 = 與目前顯示配置相反 (儲存的值 (如有),否則為系統偏好設定)。使用者意圖是「選取與目前所見相反的選項」,而非「選取與系統預設相反的選項」。
  2. 如果目標與目前的系統偏好設定不同,請直接儲存。
  3. 如果目標與目前的系統偏好設定相符,表示使用者正在復原調整:移除儲存的值。請勿儲存,否則會隱形固定配置,無法因應未來的系統變更。
  4. 將 <meta> 內容設為儲存的值,如果沒有,則設為 light dark。

重要事項:只能在儲存時檢查差異,絕不能追溯:如果系統偏好設定後來變更為與儲存值相符,則必須保留該儲存值。許多使用者的 OS 會根據時間自動切換配置;如果每次兩者剛好一致時就移除儲存的值,就無法固定配置。

範例情境 (OS 一開始設為淺色):

  1. 使用者切換。深色與系統偏好設定不同,因此系統會儲存 dark,網站會變成深色。
  2. OS 設定會變更為深色。網站會維持深色模式 (儲存的值現在與系統偏好設定相符,但會保留)。
  3. OS 設定會變回淺色。網站會保持深色模式。
  4. 使用者切換。目標 (淺色) 與系統偏好設定相符,因此系統會移除儲存的值,網站也會再次遵循系統設定。

三態切換鈕

如果空間較大,且使用者已準備好設定長期偏好設定,三態控制項可提供更清楚的資訊。在這種情況下,資料模型會清楚對應至三個 UI 選項:系統、淺色和深色。

為減輕認知負擔,「系統」選項也應顯示目前的系統設定,例如:

  • 圖示,例如系統圖示右下方的太陽圖示
  • 文字,例如「系統 (目前為淺色)」