iT邦幫忙

2026 iThome 鐵人賽

DAY 17
0
Modern Web

前端不寫 Python,照樣 ship 一把網頁無障礙 CLI系列 第 17

Day 17:我一行 CSS 都沒改,同一個顏色在深色模式下變成不合格

  • 分享至 

  • xImage
  •  

Day 17 · W3 · 無障礙線 · 難度 ★★★☆☆

本系列由 AI 協作撰寫。 內容、技術判斷、程式碼由 light-design 數位顧問團隊與 Claude 共同產出,最終由作者驗證後 publish。完整協作模式與把關方式見 Day 01

四天前我把鍵盤按過去,一站一站量焦點外框跟背後顏色的對比。33 個停留點,兩個的外框眼睛看不見。

今天我把同一個網站切成深色,再按一次。

淺色   33 個停留點,2 個不合格
深色   33 個停留點,32 個不合格

CSS 一行都沒改,連焦點外框的色碼都一模一樣。變的只有它背後那片顏色。

一句話主軸:配色切了,狀態樣式沒跟著切。

深色不是把顏色反過來,是另一組數字

prefers-color-scheme 是一個媒體查詢,跟 max-width 同一個層級。使用者在系統或瀏覽器選了深色,這段就會生效:

@media (prefers-color-scheme: dark) {
  :root { --bg: #15171A; --ink: #E4EEEC; }
}

所以深色模式不是「反色濾鏡」,是同一份 CSS 的另一個分支。設計系統通常只在這個分支裡重新定義色票,其他規則原封不動。

問題就出在原封不動的那些。對比是兩個顏色之間的關係,換掉其中一個,另一個沒動,數字就變了。

要量它必須真的開瀏覽器:

a11y-moda site https://light-design.com.tw --level AAA \
  --render --dark-mode

--dark-mode 會跑兩趟,淺色一次深色一次,然後合併。只在深色出現的問題會標上 [深色模式] 前綴,才分得出哪些是原本就有的。

代價是時間翻倍。值不值得,看你的網站有沒有那個分支。

不裝工具也看得到。Chrome DevTools 的 Rendering 面板有一個 Emulate CSS prefers-color-scheme,切成 dark,然後用 Tab 一站一站走。

麻煩的地方不是切換,是記錄。一頁三十幾個停留點,走完要幾分鐘,而且你得同時記住哪一個看得見、哪一個看不見、以及每一個的外框是什麼顏色。六個頁面走下來,這件事就從「看一眼」變成「做一份表」。

同一個藍,換一個底色

我把焦點外框的實際色值印出來,兩種模式各一次。

              淺色                深色
outline-color rgb(10, 71, 176)   rgb(10, 71, 176)
頁面底色      rgb(252,253,254)   rgb(21, 23, 26)

外框顏色一模一樣。 品牌藍在深色分支裡沒有被重新定義過。

拿 WCAG 的對比公式算一次:

品牌藍 vs 淺底    8.17 : 1     ✅
品牌藍 vs 深底    2.17 : 1     ❌  門檻 3:1

同一個色碼,一個過得很寬鬆,一個不到門檻的三分之二。

焦點外框對比對照圖:同一個色碼 rgb(10,71,176) 品牌藍,在淺色底 rgb(252,253,254) 上對比 8.17 比 1,在深色底 rgb(21,23,26) 上對比 2.17 比 1,圖中標出 WCAG 2.4.13 要求的 3 比 1 門檻線,淺色那一端遠高於門檻,深色那一端落在門檻之下

外框的色碼沒變,變的是它畫在什麼顏色上面。

會拿外框跟頁面底色比而不是跟元件自己的填色比,是因為這個外框畫在元件外面。那件事 Day 13 講過,這裡不重複。

這像是換了整個房間的燈。牆壁、地板、家具的顏色都跟著配色方案換了,只有你貼在牆上那張黃色便利貼沒換 —— 白牆上一眼看到,深色牆上就融進去了。

焦點外框就是那張便利貼。

而且它只在鍵盤操作的時候才會出現。我自己驗收深色配色是拿滑鼠點的,從頭到尾沒有一個元件顯示過焦點外框,那圈東西根本沒進到我的視野裡。

它不是內容,是狀態。而狀態樣式很少被列進配色檢查清單,因為做清單的時候它不在畫面上。

六頁攤開,只有一條規則在動

深色那趟多報了 6 條,全部是同一個檢測碼。

CS3241300E 焦點指示器對比(WCAG 2.4.13)

              淺色      深色
/             2/33      32/33
/contact      1/22      22/22
/about-us     1/18      18/18
/pricing      1/28      28/28
/works        1/22      22/22
/blog         1/51      51/51
─────────────────────────────
合計          7/174    173/174

六個頁面在淺色與深色下的焦點外框失敗數對照表:首頁從 33 個停留點中 2 個不合格變成 32 個不合格,聯絡頁 1/22 變 22/22,關於頁 1/18 變 18/18,方案頁 1/28 變 28/28,作品頁 1/22 變 22/22,部落格 1/51 變 51/51,六頁合計從 174 個停留點中 7 個不合格變成 173 個不合格

同一份 CSS,同一批停留點,只換了配色分支。

五個頁面在深色下是全滅。到這個地步已經不用數哪些不合格,要數的是還活著的那一個

部落格那頁 51 個全倒,是六頁裡最多的。停留點多是因為文章列表每一則都是連結,而每一則用的是同一個外框。

同一個錯誤複製五十一次,不會變成五十一種錯誤。它還是一個。

那個活下來的元件

首頁 33 個停留點裡,深色下有一個過關。我把外框顏色不是品牌藍的元件全部挑出來,只有兩個,其中一個是搜尋開關:

                     淺色                深色
其他所有元件          rgb(10, 71, 176)   rgb(10, 71, 176)
搜尋開關(3px 外框)  rgb( 32, 34, 37)   rgb(245,247,250)

淺色下它是深灰,深色下換成近白。有人替這一顆按鈕想過深色。

一個網站裡有一兩個元件做對、其他沒有,通常不是判斷錯誤,是那幾個元件的樣式剛好被單獨改過 —— 可能是後來補的,也可能是從別的地方複製來的。

這種「局部正確」比全錯難發現。你隨手點到它的時候會覺得沒問題。

半透明的那一條

工具報的最低對比是 1.15,比我手算的 2.17 還低。追下去是搜尋框:

outline-color   rgba(10, 71, 176, 0.25)

四分之一透明度的藍,疊在深色底上,實際呈現的顏色會非常接近底色本身,所以對比掉到 1.15。這個數字是對的:半透明的顏色要先跟背後的顏色合成,再拿合成後的結果去算對比。如果直接把 alpha 丟掉,算出來會是 2.17,反而看起來沒那麼糟。

工具在這裡做對了它該做的事。

淺色底下同一個外框是 1.54,本來就不合格。深色只是讓它更慘。

文字對比反而是乾淨的

我原本以為深色那趟會多出一批文字對比 —— 那是這個維度最典型的問題型態。結果一條都沒有。 多出來的 6 條全是焦點外框。

Day 02 提過我們曾經被指出一組深色模式下的文字對比問題,數字是 2.24。那組後來修好了,今天在深色下重新量,那個區塊乾淨。

所以「切成深色之後整個網站都要重驗」不成立。實測是只有一條規則在動,但那條規則整站全滅。這兩件事聽起來差不多,做起來完全不同 —— 前者是重來一遍,後者是改一個色票。

token 管了顏色,沒管狀態

設計系統的色票通常長這樣:

:root                                { --brand: #0A47B0; }
@media (prefers-color-scheme: dark)  { --brand: #0A47B0; }

深色分支裡把 --brand 又寫了一次,值一樣。看起來像是「已經考慮過了」,實際上只是複製貼上。

真正該被定義的不是顏色本身,是這個顏色在哪些底色上要成立--brand 當內文連結、當按鈕填色、當焦點外框,背後的顏色完全不同,能不能過門檻也完全不同。而 :focus 的樣式通常寫在元件檔案裡,離色票定義很遠,配色改版的時候沒有人會打開那個檔案 —— 色票檔改完、視覺確認過、深色分支上線,整條流程沒有任何一步會碰到它。

規範為什麼把它叫「雙色焦點指示器」

這條檢測碼的正式描述是「建立雙色焦點指示器以確保與所有組件有足夠的對比度」。第一次讀到「雙色」我以為是美術要求,其實它是在解一個幾何問題。

單色外框只有一個顏色,它必須同時跟外面的頁面底色、以及裡面的元件填色都拉開距離。兩種配色模式一乘,就是四個組合都要過。一個色碼很難同時滿足。

雙色的做法是讓外框自己帶對比:

button:focus-visible {
  outline: 2px solid #FFF;
  outline-offset: 0;
  box-shadow: 0 0 0 4px #0A47B0;   /* 外圈深、內圈淺 */
}

白色那圈貼著元件,藍色那圈落在頁面上。不管背後是淺是深,總有一圈跟它形成對比。這樣就不必為每個配色分支各調一次。

另一個更省的寫法是讓外框吃 currentColor。文字顏色本來就會隨配色切換,外框跟著走,兩邊自動成立。代價是外框顏色不再是品牌色。

今天的重點

  • 深色模式不是反色,是同一份 CSS 的另一個分支。 色票換了,其他規則原封不動,而對比是兩個顏色之間的關係。
  • 狀態樣式最容易被漏掉。 :focus:hover 寫在元件裡,配色改版時沒人會打開那個檔案。
  • 局部正確比全錯難發現。 173 個不合格裡活下來的那一個,是因為有人單獨改過它。
  • 半透明的顏色要先合成再算對比。 直接丟掉 alpha 會算出比實際好看的數字。
  • 只有一條規則在動,不等於問題小。 那條規則在六個頁面上全滅。

明天 Day 18:焦點的顏色看完了,接下來看它的順序。我的部落格上有三顆按鈕,Tab 走不到它們,因為我把它們從 Tab 順序裡拿掉了,而那樣鍵盤才算走得對。


上一篇
Day 16:我的連結沒有一個「了解更多」,問題在另一頭
系列文
前端不寫 Python,照樣 ship 一把網頁無障礙 CLI17
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言