WordPress 雙模式主題切換的自動與手動設定

深色模式已經從少數工程師的偏好變成多數人的日常設定。手機、筆電的作業系統都內建了主題切換,許多人晚上會讓整個系統翻成深色背景以減輕眼睛負擔。問題是,當這些讀者打開你的 WordPress 網站,畫面卻是一片刺眼的白底,落差感非常明顯。

要把雙模式主題切換做對,並不只是「加一段深色 CSS」這麼單純。真正完整的做法包含三件事:跟著讀者的系統偏好自動切換、提供一個讓讀者手動覆蓋的開關、以及處理好圖片與標誌在兩種背景下的呈現。少了任何一塊,使用者體驗都會出現破綻——例如自動切換做了,但手動開關按了沒反應;或是文字配色換好了,圖片卻在深色底上白得發亮。

這篇會把雙模式主題切換的完整邏輯拆開講清楚,從最基礎的 CSS 媒體查詢,到避免換頁瞬間閃白的處理,再到圖片與標誌的適配策略,讓你的佈景主題在淺色與深色之間都能維持一致的質感。

prefers-color-scheme 如何讓網站跟著系統自動切換

雙模式自動切換的核心是一個 CSS 媒體特性:prefers-color-scheme。它會偵測讀者在作業系統層級選的是淺色還是深色主題,瀏覽器再依此套用對應的樣式。讀者不需要在你的網站上做任何設定,打開頁面就會看到符合自己系統偏好的配色。

最基本的寫法是針對深色偏好定義一組覆蓋樣式。當系統處於深色模式時,下面這段才會生效:

@media (prefers-color-scheme: dark) {
  body {
    background-color: #121212;
    color: #e6e6e6;
  }
}

實務上建議深色底色不要用純黑 #000000,而是接近 #121212 的深灰。純黑配純白文字對比過強,長時間閱讀反而容易產生視覺疲勞與光暈感,這也是多數作業系統的深色介面都採用深灰而非純黑的原因。

光換 body 的底色與文字色還不夠。連結、標題、引言區塊、表格邊框、卡片陰影這些元素都各自有顏色,淺色模式下成立的配色搬到深色底上經常會「消失」或對比不足。比較有系統的做法是把顏色抽成 CSS 變數,在媒體查詢裡只改變數值,元素的樣式規則完全不用動。

:root {
  --bg: #ffffff;
  --text: #1a1a1a;
  --link: #0d6efd;
  --border: #e0e0e0;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #121212;
    --text: #e6e6e6;
    --link: #6ea8fe;
    --border: #333333;
  }
}

body { background: var(--bg); color: var(--text); }
a { color: var(--link); }
.card { border: 1px solid var(--border); }

這種變數化的結構是雙模式能否維護下去的關鍵。網站日後新增元件時,只要沿用既有變數,深色模式就自動成立,不必每加一個區塊就回頭補一段深色覆蓋。

color-scheme 與 light-dark 函式在區塊主題裡怎麼設定

如果你用的是 WordPress 區塊主題(block theme,例如 Twenty Twenty-Four 這類支援全站編輯的主題),現代 CSS 提供了一條更精簡的路徑:color-scheme 屬性搭配 light-dark() 函式。

第一步是告訴瀏覽器這個頁面支援兩種配色。在 :root 上設定 color-scheme: light dark,瀏覽器就會依系統偏好決定要用哪一套,連表單元件、捲軸這些瀏覽器原生 UI 也會跟著換成對應色系。區塊主題可以直接寫進 theme.jsonstyles.css 欄位:

{
  "version": 3,
  "styles": {
    "css": ":root { color-scheme: light dark; }"
  }
}

接著用 light-dark() 函式定義顏色。這個函式吃兩個值,第一個是淺色模式要用的顏色,第二個是深色模式要用的顏色,瀏覽器依 color-scheme 的判斷自動挑選。把它寫進 theme.json 的調色盤,整個主題的顏色就同時具備了兩套定義:

{
  "settings": {
    "color": {
      "palette": [
        { "slug": "base", "name": "Base", "color": "light-dark(#ffffff, #121212)" },
        { "slug": "contrast", "name": "Contrast", "color": "light-dark(#1a1a1a, #e6e6e6)" }
      ]
    }
  }
}

color-scheme 屬性目前在主流瀏覽器有約九成五的支援度,light-dark() 函式約八成六,對於只需支援現代瀏覽器的專案來說已經可以放心使用。它的好處是顏色定義集中在 theme.json 一處,不必另外維護一份深色覆蓋的樣式表。

這個方法有一個目前無解的限制要先知道:WordPress 的網站編輯器尚未提供「同時設定淺色與深色兩組色碼」的介面。如果網站使用者透過樣式面板覆蓋了調色盤裡的某個顏色,那個自訂色會在兩種模式下都套用,等於把 light-dark() 的雙值定義蓋掉。對外散布的佈景主題或客戶能自行改色的網站,這點需要事先跟對方說明。

自動切換之外,為什麼還要做手動開關

prefers-color-scheme 只反映作業系統的設定,這是它最大的優點,也是最大的限制。有些讀者的系統整天維持深色,但看你的網站時偏好淺色;也有人系統是淺色,但進到長文閱讀頁想切深色。純靠媒體查詢,這些人沒有任何選擇權。

要給讀者覆蓋的能力,常見做法是在 <html> 元素上掛一個 data-theme 屬性,並讓它的優先序高於媒體查詢。CSS 改成這樣安排:

:root {
  --bg: #ffffff;
  --text: #1a1a1a;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) {
    --bg: #121212;
    --text: #e6e6e6;
  }
}
:root[data-theme="dark"] {
  --bg: #121212;
  --text: #e6e6e6;
}
:root[data-theme="light"] {
  --bg: #ffffff;
  --text: #1a1a1a;
}

這裡的設計邏輯是三種狀態。當 <html> 沒有 data-theme 屬性時,代表讀者沒手動選過,交給 prefers-color-scheme 跟著系統走;一旦讀者點了開關,就在 <html> 寫上 data-theme="dark"data-theme="light",這組規則的權重高於媒體查詢,於是覆蓋系統偏好。注意媒體查詢那段加了 :not([data-theme]),確保手動選擇後系統偏好不會再回頭干擾。

開關本身用一個按鈕加少量 JavaScript 處理。點擊時切換 data-theme 的值,並把讀者的選擇存進 localStorage,下次再訪才會記得。

<button id="theme-toggle" aria-pressed="false" aria-label="切換深色模式">切換主題</button>
const root = document.documentElement;
const btn = document.getElementById('theme-toggle');

btn.addEventListener('click', () => {
  const current = root.getAttribute('data-theme');
  const next = current === 'dark' ? 'light' : 'dark';
  root.setAttribute('data-theme', next);
  localStorage.setItem('theme', next);
  btn.setAttribute('aria-pressed', next === 'dark');
});

無障礙細節不要省略。按鈕要有 aria-label 說明它的作用,並用 aria-pressed 標示目前處於開或關,讓使用螢幕報讀軟體的讀者也能掌握狀態。圖示如果是純裝飾性的 SVG,記得加上 aria-hidden="true",避免報讀軟體念出無意義的內容。

換頁瞬間閃白的成因與解法

把開關做好之後,常會遇到一個惱人的現象:讀者明明選了深色,但每次換頁或重新整理,畫面會先閃一下白底再變回深色。這個「無樣式內容閃現」的英文縮寫是 FOUC,成因在於執行順序。

瀏覽器收到 HTML 後會先以預設的淺色樣式把頁面畫出來,等到頁尾或外部檔案裡的 JavaScript 載入並執行,才從 localStorage 讀出讀者的偏好、補上 data-theme="dark"。從畫面渲染到屬性補上之間有一小段時間差,深色偏好的讀者就會看到那一閃的白。

解法是把讀取偏好、設定屬性這段邏輯,用一段內嵌(inline)的 JavaScript 直接寫進 <head>,而且要放在所有 CSS 與 HTML 主體渲染之前。這段程式碼在瀏覽器畫任何東西以前就跑完,data-theme 早就掛好,自然不會閃。

<head>
  <script>
    (function () {
      const saved = localStorage.getItem('theme');
      if (saved) {
        document.documentElement.setAttribute('data-theme', saved);
      }
    })();
  </script>
  <!-- 其餘 CSS 連結放在這段之後 -->
</head>

判斷邏輯是這樣:如果 localStorage 裡存有讀者手動選的偏好,就直接套用;沒有的話就不掛 data-theme,讓 prefers-color-scheme 接手跟著系統。因為這段是內嵌而非外部檔案,瀏覽器不必等額外的網路請求就能執行,這正是它能搶在渲染前完成的原因。

在 WordPress 裡實作這段時,要把它掛到 wp_head 這個鉤子上,並確保它的優先序夠前面,排在主題樣式表載入之前輸出。一個常見錯誤是把切換邏輯全部寫進主題的主 JavaScript 檔,那支檔案通常排在頁尾載入,等它跑完早就閃過去了。

圖片與標誌在兩種模式下的適配策略

文字配色處理好,圖片往往是被忽略的最後一塊。深色底配上原本為白底設計的圖片,最常見的兩個問題是:透明背景的標誌變得幾乎看不見,以及含有大片白底的截圖、圖表在深色頁面上白得刺眼。

不少教學會建議直接把所有圖片在深色模式下調暗,用 filter: brightness(.75) 或降低不透明度。這個做法要謹慎使用。把所有內容圖片一律調暗會降低整體對比,含有文字或重要細節的圖反而變得難以辨識;對因為光敏感等無障礙需求而開深色模式的讀者來說,這甚至會讓圖片更難看清。更糟的是有些寫法把圖片設成滑鼠懸停才恢復亮度,使用鍵盤瀏覽的讀者根本無法觸發,等於看不到完整的圖。所以全域調暗不是好的預設策略,照片類的內容圖通常維持原樣即可。

真正需要換版本的是標誌與帶有大片白底的圖示。最乾淨的做法是用 HTML 的 <picture> 元素搭配媒體查詢,依系統偏好挑選不同檔案,瀏覽器原生支援,不需要 JavaScript:

<picture>
  <source srcset="/logo-dark.svg" media="(prefers-color-scheme: dark)">
  <img src="/logo-light.svg" alt="網站標誌">
</picture>

深色模式時瀏覽器挑第一個 <source>,載入專為深色底設計的白色或反相版標誌;其餘情況落回 <img> 的淺色版。這裡有個需要留意的地方:<picture>media 條件只認得系統的 prefers-color-scheme,不會理會你用 data-theme 做的手動開關。也就是說,讀者手動切到深色但系統還是淺色時,這張圖不會跟著換。

如果你的網站重視手動開關與圖片的一致性,可以改用 CSS 控制兩個版本的顯示。準備兩張圖疊在容器裡,用 data-theme 與媒體查詢共同決定誰顯示、誰隱藏,邏輯就能和文字配色完全對齊。背景圖(CSS background-image)也適用同樣的策略,在對應的選擇器底下換 url() 即可。

對 WooCommerce 這類電商網站,商品圖大多是去背或白底的去背圖,這類圖在深色頁面通常不需要動,維持白底反而能讓商品本身更突出。需要特別處理的是品牌標誌、付款方式圖示、信任標章這些介面元素——它們多半是深色線條配透明底,深色模式下會直接糊掉,建議比照標誌準備反相版本。涉及金流的頁面只要把這些圖示的可見度顧好即可,配色邏輯與一般頁面並無不同。

無論用哪種方式換圖,替代文字(alt)都要寫好。如果兩個版本只是顏色不同、傳達的資訊一致,共用同一段 alt 沒問題;但若不同版本想傳達的氛圍或內容有差異,分別寫對應的描述能讓使用輔助技術的讀者得到更貼近的體驗。

把自動、手動與圖片三塊組起來的順序

雙模式主題切換能不能讓讀者覺得順手,關鍵不在用了多炫的技術,而在三塊有沒有接好:prefers-color-scheme 負責讓網站開箱就跟著系統走,data-themelocalStorage 給讀者覆蓋與記憶的能力,<head> 內嵌指令清掉換頁的閃白,最後用 <picture> 或 CSS 把標誌與白底圖示的呈現補齊。

實作時建議照這個順序推進。先用 CSS 變數搭配 prefers-color-scheme 把兩套配色立起來,確認純自動切換的狀態正常;再疊上 data-theme 的手動開關與權重規則;接著補 <head> 內嵌指令解決閃白;圖片留到最後,逐一檢查標誌與介面圖示在深色底上是否清楚。每加一層就在淺色、深色、手動覆蓋三種情境各看一遍,問題會比全部寫完再回頭除錯好抓得多。

雙模式不是一次寫完就結束的功能。日後新增區塊、換主題版本、上架新商品時,都用回既有的 CSS 變數與圖片策略,深色模式就會自動成立。把這套結構先打穩,往後每一篇文章、每一個頁面都能在淺色與深色之間維持一致的閱讀品質。

相關文章
標籤: FOUC, 圖片適配, WordPress 主題, prefers-color-scheme, 深色模式