Day 05 · W1 · 無障礙線 · 難度 ★★☆☆☆
本系列由 AI 協作撰寫。 內容、技術判斷、程式碼由 light-design 數位顧問團隊與 Claude 共同產出,最終由作者驗證後 publish。完整協作模式與把關方式見 Day 01。
Day 4 結尾我說這三個工具都跑過。今天把數據攤開。
先看一行 HTML:
<input type="text" placeholder="姓名">
axe-core 判「過」。a11y-moda 判「不過」。
兩邊都沒錯。 這篇就是在解釋為什麼會這樣,以及為什麼這正是我還要再蓋一把的理由。
一句話主軸:在台灣送件,你對的是碼表,不是條文。
「不要重複造輪子」是對的。但判斷重不重複之前,得先看兩個輪子裝在哪一台車上。
兩個目標,都可以重跑:
一、W3C WAI 的 Before and After Demonstration。 W3C 自己做的教學站,同一個網站有故意做壞的 before 版跟修好的 after 版,網址就是 w3.org/WAI/demos/bad/before/home.html 跟 /after/home.html。
二、Day 4 那個八行 fixture。 我親手埋了六個問題進去。
我沒有拿任何營運中的網站當負面案例。一來不厚道,二來它隨時可能改版,讀者跟著跑會對不上。BAD 是公開、固定、W3C 本來就標明用途包含「拿工具評估」的素材,對四個工具一視同仁。
fixture 的作用不一樣:我知道裡面有什麼,所以能算召回率,而不只是比誰報得多。
a11y-moda scan <url> --level AA --render
npx pa11y --standard WCAG2AA --reporter json <url>
lighthouse <url> --only-categories=accessibility --output=json
# axe-core 走 Playwright 注入 axe.min.js 後 axe.run()
| 工具 | before(故意做壞) | after(W3C 修好版) |
|---|---|---|
| a11y-moda | 11 fail · 6 info · 1 caveat | 4 fail · 4 info · 1 caveat |
| axe-core 4.13 | 6 規則 / 46 節點 | 1 規則 / 2 節點 |
| pa11y | 41 則 | 0 則 |
| Lighthouse | 分數 50 · 7 項不過 | 分數 91 · 2 項不過 |
這張表不能直接比大小,因為單位不一樣。
pa11y 的 41 跟 axe 的 46 是同一量級,兩個都是逐元素算;axe 的 6 條規則跟我們的 11 條才是同一量級,兩個都是逐規則算。把 41 跟 6 放在一起說「pa11y 抓得比較多」,等於比較兩家醫院一年處理幾例,一家算病人數、另一家算科別數。

同一頁、同一天跑出來的四組數字。要比之前得先把單位對齊,否則整張表都是誤導。
<input>,兩邊都對先確認 axe 不是沒跑到那條規則。我單獨指定規則再跑一次:
passes label nodes=1 <input type="text" placeholder="姓名">
passes link-name nodes=1 <a href="/pricing">按這裡</a>
violations color-contrast nodes=1 <p style="color:#999;background:#fff">小字說明</p>
label 那條是明確的 passes,不是未執行。
原因很正當:placeholder 依照 accessible name 的計算規格確實會成為那個輸入框的名字,所以 WCAG 4.1.2「名稱、角色、值」這關過得去。axe 沒有做錯任何事。
MODA 碼表的寫法不一樣。HM1130103C 的原文是「可見的表單控制元件均需有對應的標籤組件,或有標題屬性,且其內容或值均不得為空字串或空白」。它明文指定了實作方式,而不只是要求結果。placeholder 不是 <label>、也不是 title,所以我們 fail。
同一個輸入框,兩個工具給相反的答案,而兩邊都符合各自對的那份文件。
順帶一提,<a href="/pricing">按這裡</a> axe 也判過,因為 link-name 只查連結有沒有文字,不判斷那段文字講不講得清楚要去哪。這一格我們也沒抓到。這輪沒有開 LLM,數字全部是純機器規則的結果。
BAD 只能比「誰報了什麼」,比不了「誰漏了什麼」。要看漏抓,得用有標準答案的題目。
Day 4 那八行裡我埋了六個:圖片沒有 alt、div 綁 onclick 沒有鍵盤事件、<html> 沒有 lang、輸入框只有 placeholder、連結寫「按這裡」、#999 配白底對比 2.85。
| 工具 | alt | 鍵盤 | lang | label | 按這裡 | 對比 | 命中 |
|---|---|---|---|---|---|---|---|
| a11y-moda | 抓到 | 抓到 | 抓到 | 抓到 | 漏 | 抓到 | 5/6 |
| pa11y | 抓到 | 漏 | 抓到 | 抓到 | 漏 | 抓到 | 4/6 |
| axe-core | 抓到 | 漏 | 抓到 | 漏 | 漏 | 抓到 | 3/6 |
| Lighthouse | 抓到 | 漏 | 抓到 | 漏 | 漏 | 抓到 | 3/6 |
三件事值得看。
四個工具都抓到的那三格(alt、lang、對比)是行業共識,不管你用哪一把都不會漏。這也代表如果你的網站連這三樣都在掉,換工具不會解決問題。
div 加 onclick 沒有鍵盤事件只有我們報。 這條在碼表裡是 GN1210100E,明文要求滑鼠事件要有鍵盤等效。它不難檢查,只是不在其他三個工具的題目範圍裡。
「按這裡」四個全滅。 這格不是誰的疏失,是機器規則到不了的地方 —— 要判斷「這段連結文字有沒有講清楚要去哪」,需要理解語意。這正是那些人工判斷規則存在的理由,也是我們把 LLM 接進來的位置。
after 版的殘留很有意思:
| 工具 | 還在報什麼 | 屬於 |
|---|---|---|
| axe / Lighthouse | target-size |
WCAG 2.2 的 2.5.8 |
| Lighthouse | landmark-one-main |
ARIA 慣例,非準則明文 |
| a11y-moda | 320px 下仍需水平捲動 | WCAG 2.1 的 1.4.10 |
| pa11y | 無 | — |
我原本以為這是「我們比較嚴」,看完才發現不是。axe 跟我們指到的是同一件事:這一頁是 WCAG 2.0 時代做的。 2.5.8 是 2.2 才有的準則,1.4.10 是 2.1 才有的,BAD 當年沒有理由符合它們。
兩個工具從完全不同的角度得到同一個結論,這比我單方面宣稱「那份素材太舊」可信得多。
至於 pa11y 的 0,它的引擎只做到 WCAG 2.0,所以那個 0 是誠實的,在它的規範版本裡確實沒問題。
「0 個問題」有時候是引擎版本的資訊,不是網頁的資訊。 看到全綠先問一句:它在看哪一版。
BAD 的 before 版裡,這四條其他三個工具都沒報:
| rule_id | WCAG | 內容 |
|---|---|---|
CS1110114E |
1.1.1 | 1×1 透明 GIF 用於版面,請改用 CSS |
CS1130103E |
1.3.1 | 使用已淘汰的 <font> 元素 |
HM1240404E |
2.4.4 | 「Read More...」重複 2 次卻指向不同網址 |
GN1210100E |
2.1.1 | <td> 有 onmouseover 但沒有 onfocus |
這不是「我們比較強」。是 MODA 把準則具體化到了這個層級,而 WCAG 條文停在更上面一層。
看那條 <font> 就懂了。WCAG 1.3.1 要求的是「結構與關聯要能被程式判定」,它從頭到尾沒提過任何一個 HTML 元素的名字,因為條文要能適用二十年。碼表可以,也必須具體:它直接點名已淘汰的呈現性元素。axe 不報這個不是它漏了,是那條規則在它的世界裡沒有依據可以成立。
判定其實有三層,分清楚才不會吵錯架:

三層一路往下越來越具體。吵「誰對」多半是因為兩邊站在不同層。
第三層是我們自己的,所以要自己招:GN1240103E 報「55 個連結未用結構性組件分群」,碼表寫的是「使用結構性組件來將鏈結分群」,那個 55 是我們挑的。HM1130100C 報「不要超過一個 h1」,碼表只說「按照正確的巢狀層次結構」,單一 h1 那句也是我們加的。
跟 Day 4 那個 _MAX_LABEL_LEN = 16 一樣,都是寫死在原始碼裡、使用者改不動的數字。
主張只有一句「對的規範不同」。以下四件我不主張,寫清楚免得被讀成別的意思:
一、不是檢得比較準。 BAD 的 after 版我們報四條,裡面就有第三層那些自訂門檻。
二、不是覆蓋比較全。 我們的規則落在 110.07 碼表內的有 124 條,碼表總共 240 條 —— 52%,一半剛過。 剩下那 116 條有些是還沒寫,有些是機器本來就判不了,得靠人看畫面、聽報讀、實際操作一遍流程才有答案。所以「送件前跑一次工具就安心」這種話我講不出口,它頂多幫你把明顯的先清掉,讓人力留給真正需要判斷的部分。另外 axe 在 ARIA 那一塊比我們深得多,那是它十幾年累積的地方,我們短期內追不上,也沒打算追。
三、不是取代誰。 官方判定是官方的,我們是協助工具,碼表跟著公布版本走。
四、出了 MODA 送件情境,這個差異歸零。 如果你的網站不需要送台灣的無障礙標章,axe-core 是更好的選擇:社群大、更新快、生態成熟,我們沒有任何理由建議你捨它就我。
所以這到底算不算造輪子? 在 WCAG 的世界裡算,在 MODA 碼表的世界裡不算。 你在哪個世界,決定了這個答案。
<input>,axe 對、我們也對 —— 對的規範不一樣,這是規範層級差異不是工具高下明天 Day 6:那份碼表 11 月 30 日換版。我算出來要補 109 條,後來發現這個數字是錯的。