承昨日把 aria-live 這個「動態內容通知」的機制拆開了,今天把它落到最需要它、也最常被做壞的場景:表單的錯誤提示。表單是整個網站無障礙的壓力測試——它同時考角色、名稱、狀態,還加上「使用者填錯了,你怎麼讓每一種人都知道錯在哪、怎麼改」。而現實是,一顆紅框加一行紅字,對螢幕報讀器使用者、對色盲使用者,幾乎等於什麼都沒說。
📖 學
先認清 WCAG 對錯誤提示的兩條硬規則,它們都是最低門檻的 Level A。第一條是 3.3.1 錯誤識別(Error Identification):只要系統自動偵測到輸入錯誤,就必須「用文字」把出錯的欄位指出來、並描述錯在哪。關鍵字是「用文字」——把邊框變紅、把欄位標籤染色,這些純視覺手段一律不算數,因為報讀器讀不到顏色,色盲使用者也分不出來。第二條是 3.3.3 錯誤建議(Error Suggestion):如果你知道正確的格式或修法,就要一併給出建議。「Email 格式錯誤」只做到及格;「Email 需包含 @,例如 name@example.com」才是真的幫上忙。
接著看報讀器這端。昨天講過,報讀器只認角色、名稱、狀態,而且不會主動去念一個焦點沒到過的區域。表單錯誤正好踩中這個陷阱:使用者按下「送出」,畫面上冒出三行紅字,他卻毫無所覺,因為焦點還停在按鈕上。要讓錯誤「被聽見」,你需要兩件事把訊息接上無障礙樹:
第一,把錯誤訊息綁到欄位上。用 aria-describedby 讓輸入框指向那段錯誤文字的 id,這樣使用者一聚焦到這個欄位,報讀器唸完標籤後就會接著唸出錯誤描述。同時給欄位加 aria-invalid="true",報讀器會在念名稱時補一句「無效」的狀態。這兩個屬性是一組的:aria-invalid 說「這格錯了」,aria-describedby 說「錯在哪、怎麼改」。
<label for="email">電子郵件</label>
<input
id="email"
type="email"
aria-invalid="true"
aria-describedby="email-error"
>
<p id="email-error" class="error">
請輸入有效的電子郵件,需包含 @,例如 name@example.com
</p>注意這裡的順序:錯誤還沒發生時,欄位上不該有 aria-invalid="true"(或設為 false),email-error 那段也應該是空的或不存在。只有在驗證失敗、你把錯誤文字填進去的當下,才把 aria-invalid 切成 true。別讓欄位一載入就宣告自己「無效」。
第二,送出失敗時給一份錯誤摘要,並把焦點送過去。單靠 aria-describedby 只能在使用者「主動走到」某個欄位時才被念到;但如果他一次填錯五格,你需要在送出後立刻告訴他「有五個問題」。作法是在表單頂端放一個錯誤摘要區塊——一份可點擊的清單,每一項連到對應欄位——並在送出失敗後,用 JavaScript 把鍵盤焦點 focus() 移到這個摘要的標題上。焦點一移過去,報讀器就會立刻念出「表單有 5 個錯誤」,使用者可以逐條點進去修。這比把整份表單包成 aria-live="assertive" 去硬念要好得多,也符合「摘要 + 焦點管理」這個被反覆驗證有效的模式。
那麼即時驗證(inline validation)呢?就是使用者一邊打字、一邊跳紅字那種。它體驗上很吸引人,但無障礙上暗藏兩個坑。一是時機:別在使用者還在打字(每個 keystroke)時就報錯,那會讓報讀器一直打斷、也讓明眼人焦躁。較穩的作法是「on blur」——使用者離開欄位時才驗證單一欄位,送出時才做整體驗證。二是朗讀:如果錯誤是即時冒出來的,而使用者的焦點還在該欄位裡,aria-describedby 不會自動重念,這時你可以把該欄位的錯誤容器也標上 aria-live="polite",讓新出現的訊息被溫和地播報一次。
最後一個常被忽略的原則:不要只靠顏色。這其實是另一條 WCAG 準則(1.4.1 顏色的運用)。紅色邊框要搭配一個錯誤圖示加文字,錯誤文字本身也要和背景有足夠對比。一個好用的自我檢查:把整個頁面轉成灰階,如果你還能一眼看出哪一格錯了,才算過關。
🧠 記
- WCAG 3.3.1 錯誤識別(Level A):偵測到錯誤必須「用文字」指出欄位並描述問題,純視覺(紅框、變色)不算。
- WCAG 3.3.3 錯誤建議(Level A):知道正確格式就要給建議。「格式錯誤」不夠,「需包含 @,例如 name@example.com」才好。
- 欄位綁錯誤訊息用一組屬性:
aria-invalid="true"宣告狀態 +aria-describedby指向錯誤文字的id。 - 錯誤未發生時
aria-invalid應為false或不存在,別讓欄位一載入就自稱無效。 - 送出失敗:在表單頂端給可點擊的錯誤摘要,並用
focus()把鍵盤焦點移過去,勝過整頁assertive硬念。 - 即時驗證用「on blur」而非每個按鍵;焦點還在欄位內時,錯誤容器可加
aria-live="polite"補念。 - 不要只靠顏色(1.4.1):紅框要配圖示與文字;灰階測試能一眼看出錯誤才算過關。
✍️ 實踐
挑你手上任一個含驗證的表單,做四步改造。第一步,找出所有「錯了才變紅」的欄位,替每個錯誤訊息元素加上 id,並在對應輸入框補上 aria-describedby 指過去、同時在錯誤狀態下設 aria-invalid="true";順手確認未出錯時這個屬性是 false 或拿掉。第二步,把每一句錯誤文字重寫一次:從「欄位錯誤」升級成「錯在哪 + 怎麼修 + 舉例」,對照 3.3.3 的標準。第三步,在表單頂端加一個錯誤摘要區塊(一份 <ul> 連到各欄位),在送出失敗的 handler 裡把焦點 focus() 移到摘要標題。第四步,打開系統報讀器(Mac Cmd+F5 開 VoiceOver),故意填錯送出,閉眼確認你能聽到「有幾個錯、每格錯在哪」;再把頁面截圖轉灰階,檢查沒有顏色時錯誤是否仍然可辨。四步做完,你的表單就從「看得到錯」進化到「聽得到、也修得動」。
🔗 延伸學習
- WCAG 2.2 Understanding 3.3.1 Error Identification — W3C 官方對「錯誤識別」準則的說明與技術範例
- WCAG 2.2 Understanding 3.3.3 Error Suggestion — 「錯誤建議」準則,教你把提示寫成可修復的指引
- WebAIM: Usable and Accessible Form Validation and Error Recovery — 表單驗證與錯誤回復的完整實作技巧
- MDN: aria-invalid —
aria-invalid屬性的用法與狀態值
💬 問 AI
我在做一個含驗證的前端表單,想讓錯誤提示對螢幕報讀器與色盲使用者都友善。請依 WCAG 2.2 幫我:
1. 檢查每個欄位的錯誤訊息是否用 aria-describedby 綁到輸入框、並在錯誤狀態設 aria-invalid="true"(未出錯時應為 false 或不存在);
2. 對照 3.3.1(錯誤識別)與 3.3.3(錯誤建議),把我的錯誤文字改寫成「錯在哪 + 怎麼修 + 舉例」;
3. 設計一個送出失敗後的錯誤摘要區塊,並示範如何用 JavaScript 把焦點移到摘要;
4. 指出我哪裡「只靠顏色」表達錯誤,建議搭配的圖示與文字寫法。
以下是我的表單程式碼:(貼上你的 HTML / JSX)