Accessibility開發與實踐系列
無障礙網頁開發:表單基礎概念與實作,以 React 為例
從語意結構、鍵盤操作與表單驗證三大面向,理解無障礙開發基礎
前言:為何要開發無障礙表單?
- 表單是前端互動關鍵的一環:在現代網頁中,表單可說是無處不在,從登入、註冊,到進行購物付款,表單都是使用者與網站互動的主要方式之一。這就表示,表單的可用性會直接影響到使用者的體驗和滿意度。對於網頁的搜尋排名 (SEO),無障礙也是關鍵指標之一。
- 無障礙是為了所有人:我們常會先入為主的認為,無障礙設計只是為了少數有障礙的人,但其實在人生中我們多少有陷入不方便的時刻,例如受傷骨折手不能動、接受手術後短暫失去視力等等。此外,一些無障礙設計,例如顏色對比度、字體大小,也可以讓一般使用者受益。因此,無障礙絕對不僅僅是針對「有障礙的人」。
無障礙表單基礎概念
在前端應用程式中,表單互動可以很簡單也可以很複雜,從最基本的聯絡表單,到包含多種欄位的多頁式表單,單靠一篇文章的篇幅可是說不完的。但我們可以從以下三個面向來探討一個具有好的可用性的表單該具備哪些條件:
語意結構、標籤與說明
語意與 Accessibility Tree
首先,我們要先了解為何清晰的語意對網頁無障礙來說如此重要。
瀏覽器在載入網頁時,會根據 HTML 結構生成兩棵樹:DOM Tree 和 Accessibility Tree。而輔助科技主要依賴 Accessibility Tree 來理解網頁的結構和內容。如果網頁中使用了適當的語義化 HTML 元素,這些輔助科技就能夠準確地解讀並與網頁互動。例如,使用 <button> 標籤來表示按鈕,螢幕閱讀器會告訴使用者這是一個按鈕,並提供相應的操作選項。
延伸閱讀:Accessibility Tree:細心的栽種打造無障礙頁面
當我們使用缺乏語意的元素來製作網頁,輔助科技就會不知道現在正在和什麼元素做互動。雖然現代前端框架(如 React、Vue.js)提供了很大的彈性,可以用自定義的方式來開發 UI 元件,但我們應該盡量使用原生的 HTML 元素來實現功能。當原生元素無法滿足需求時,我們才來思考使用 ARIA 標籤來補充語意,以確保自定義元件也能夠被輔助科技正確理解和使用。
關於語意結構的重要實作要點
- 使用 <form> 元素:會產生地標的語意,除了可以讓語意更加豐富以外,也可以允許瀏覽器用原生的操作方式送出表單資料。
- 正確的關聯欄位標籤與欄位說明:
- <input> 與 <label>: 使用 <label> 元素的 for 屬性,以及 <input> 上的 id 屬性來關聯欄位與欄位標籤,瀏覽器會使用 <label> 的內容作為欄位的可訪問名稱。
- aria-labelledby: 若標籤文字已存在其他元素上,aria-labelledby 屬性可以將一個元素與另一個已存在的標籤元素關聯起來。
<!-- 當搜尋輸入框旁邊已經有一個 "Search" 按鈕時,
使用 aria-labelledby 以按鈕文字作為欄位可訪問名稱 -->
<form>
<input
type="search"
name="search"
placeholder="Search"
aria-labelledby="search-button"
/>
<button type="submit" id="search-button">
Search
</button>
</form>
3. aria-describedby: 若欄位附近還有其他說明文字,aria-describedby 可以將元素與其他描述性內容關聯起來。
<!-- 欄位有額外的說明時,可以用 aria-describedby 關聯欄位與說明 -->
<form>
<label for="password">密碼:</label>
<input
type="password"
id="password"
name="password"
aria-describedby="password-help"
/>
<span id="password-help">
密碼必須包含至少8個字符,其中至少有一個大寫字母和一個數字。
</span>
<!-- ... -->
</form>
- 適當使用 <fieldset> 與 <legend>: 若輸入欄位為一群組時,例如核取方塊與單選按鈕 (radio),可以用 <fieldset> 將輸入關聯起來,並用 <legend> 提供該群組說明。
- 使用對應的 <input> 類型:在行動裝置上,會有對應的鍵盤出現,例如 type=”number” 會出現數字鍵盤,type=”date” 會出現日期選擇器。
- 留意符號的意義是否有正確傳達:例如,表單中常使用星號 (*) 表示必填,但星號代表必填的意義未必能完整傳達給輔助科技使用者,我們可以在表單最前面加上一段敘述:有星號(*)欄位表示必填,並確保 <input> 元素有添加 required 或 aria-required 屬性。
❌常見錯誤:用 placeholder 取代欄位標籤
在設計與開發表單時,一個常見的錯誤是用 placeholder 來取代欄位標籤。對明眼的使用者來說,這可能是個可行的做法,但從輔助科技的角度來看,即便一些輔助科技是可以報讀 placeholder 資訊的,但根據 WCAG 官方建議,placeholder 的支援度仍不完善,也不會被視為標籤,因此在實作上還是要以 <label> 為主。
此外,這個設計對一般使用者來說也有不方便的地方,因為在輸入的時候,placeholder 會消失,記性不好的使用者就不知道自己在輸入的是哪一個欄位了。
- 解決方式1:正確連結欄位與標籤。可以使用 <label> 元素的 for 屬性,以及 <input> 上的 id 屬性來實現:
<label for="username">帳號:</label>
<input type="text" id="username" name="username" placeholder="輸入您的帳號" />
<label for="email">電子郵件:</label>
<input type="email" id="email" name="email" placeholder="輸入您的電子郵件" />
- 解決方式2:使用視覺上隱藏的標籤。在 Tailwind CSS 中,可以使用 “sr-only” 這個 utility class 來設定:
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border-width: 0;
}
⚠️提醒:使用 ARIA 來讓語意更加豐富,但要小心使用
當原生的元素不足以提供足夠的語意時,使用 ARIA(Accessible Rich Internet Applications)標籤可以用來為輔助科技提供更多的訊息。但在使用 ARIA 屬性時要小心使用,因為過度使用是可能造成反效果的,記得這句話:No ARIA is better than Bad ARIA!
鍵盤操作
WCAG Level A Success Criteria: Keyboard Accessible
在 Web Content Accessibility Guidelines (WCAG) 所公布的 Level A 成功標準中,2.1.1 Keyboard Accessible 這一項標準要求所有功能都必須能夠使用鍵盤操作,而不必依賴滑鼠或其他設備。例如,某些使用拖放 (drag-and-drop) 操作的功能應該也要提供鍵盤的操作方式。
確保每個表單元素都是可用鍵盤來操作的
- 使用原生的 HTML 元素:例如 <select> 或原生的 radio input、date picker 等,都提供了完整的無障礙操作,因此,為了確保表單能夠使用鍵盤操作,我們應該優先使用原生元素,而非使用手刻的元件。
- 使用受到肯定的元件庫:由於設計需求與開發效率等考量,在開發的時候我們經常會使用元件庫來實作。常見的元件庫通常都有不錯的無障礙支援,例如 MUI、Chakra UI 等。若有客製化樣式的需求,也可以使用無樣式的 UI 庫,例如 Headless UI、Radix UI、React Aria 等。
- 參考 APG Patterns 來實作:若遇到非不得已,一定要用手刻的方式來開發元件時,可以參考 ARIA Authoring Practices Guide (APG) 所提供的設計與開發模式指南來設計與實作無障礙功能,避免誤用 ARIA 反而對無障礙造成了損害。
設定清楚的焦點樣式
對於低視能、肢障等必須依賴鍵盤操作的使用者來說,網頁中的元素具有清晰的焦點樣式是非常重要的。在表單中更是如此,使用者必須清楚知道現在正在和哪個元素做互動。因此,最重要也最關鍵的一點,是「不要關閉瀏覽器預設的焦點樣式!」
* {
outline: none; /* 這樣非常不好! */
}
如果在設計上並沒有針對每種元素客製化特殊的焦點樣式,可以使用雙層的黑白焦點樣式,確保焦點在深/淺色背景中都能被看見。
:focus-visible {
outline: 9px double black;
box-shadow: 0 0 0 6px white;
}
延伸閱讀:焦點的無障礙 — 焦點可見與樣式篇
表單驗證與錯誤訊息
在開發表單時,除了讓使用者可以順利的填寫之外,在送出表單後,讓使用者知道有錯誤發生了,以及該如何修正也是表單操作體驗很重要的一環。
正確提示與關聯錯誤訊息
- aria-invalid:當欄位有錯誤時,通常我們會使用視覺上的樣式改變來傳達該欄位有誤,不過,這個方法未必能很好的傳遞給輔助科技的使用者。因此,使用 aria-invalid 可以多補充一些語意,例如,螢幕閱讀器會報讀「無效的輸入」。
<label for="email">電子信箱:</label>
<input type="text" id="email" aria-invalid="true">
- aria-describedby:對明眼人來說,在欄位附近顯示錯誤訊息可能很直覺,但是對於輔助科技來說,我們必須關聯此描述與欄位,才能將訊息正確傳遞給使用者。aria-describedby 屬性可以將一個欄位與一個或多個描述性元素關聯起來,提供更多關於錯誤的訊息。
<label for="username">帳號:</label>
<input type="text" id="username" aria-invalid="true" aria-describedby="usernameHint usernameError" />
<span id="usernameError">帳號已有人使用。</span>
<span id="usernameHint">帳號需小於 20 字元。</span>
Focus 到第一個錯誤的欄位
當我們送出表單後,若驗證失敗,通常會使用醒目顏色和錯誤訊息來提示使用者,但是對於螢幕閱讀器使用者來說,若缺乏適當的回應,他們會無法知道現在表單中哪些欄位有誤。常見的一個方式是是用焦點控制 (focus control) 來告知使用者發生錯誤的欄位,在表單送出後,我們可以使用 element.focus() 方法將焦點移至錯誤的欄位上。
這樣做的好處是,使用者可以馬上對錯誤的欄位進行修正,而不用穿梭在各個欄位中找到有錯的那一個。
延伸閱讀:焦點無障礙 — 管理焦點的行為與轉移
使用 ARIA Live Regions 來通知使用者
除了焦點控制,我們也可以使用 ARIA Live Regions 來通知使用者有錯誤發生。ARIA Live Regions 允許開發者向輔助科技的使用者提供即時訊息,使用的情境包括即時聊天、表單驗證、購物車等等。我們只要在元素上使用 ARIA 標籤中的 aria-live,就能標示某個區域為 Live Regions。
很多表單會統一在表單最上方列出哪些欄位有誤,我們可以將這個元素變成一個 Live Region,確保螢幕閱讀器使用者可以接收到這些訊息。
<div aria-live="polite" id="form-status">註冊失敗,無效的信箱和密碼。</div>
<form>
<!-- ... -->
</form>
不過,當錯誤的欄位較多時,一次說明哪些欄位錯誤對使用者來說反而並不方便,報讀過後也忘記有哪些欄位需要修改了。 因此,我們可以改用輸入後及時驗證,或是僅用焦點控制加上適當的訊息關聯,這樣就可以大幅改善無障礙體驗了。
延伸閱讀:使用 ARIA Live Regions 讓重要資訊不漏接 (上)
❌常見錯誤:在未完成填寫時禁用送出按鈕
有時設計師或開發者會在表單未完成填寫時禁用提交按鈕,這樣的設計原本用意在於避免使用者在還沒有正確填寫完畢的時候提交表單,但是這會產生幾個問題:
- Disabled 狀態無法有效說明發生什麼問題,使用者必須自行猜測禁用的原因。
- Disabled 狀態會導致使用者無法與按鈕互動來得到一些回饋。更好的做法是允許提交,但在提交時進行驗證,並告知使用者錯誤。
測試方法
在開發完無障礙功能後,進行測試也是很重要的步驟,這邊說明一些簡單有效的測試方式:
基本測試方法:
- 鍵盤導航與焦點樣式:我們可以使用 Tab 鍵逐一與表單元素互動,確保每個元素能夠被聚焦、焦點順序邏輯正確,也都能正常使用。另外也要確認每個表單元素獲得焦點時應該有明顯的焦點樣式。
- 觀察 Accessibility Tree:透過瀏覽器 devtools 中的 Accessibility Tree 和相關的面板,我們可以確認每個表單元素的語意有沒有正確顯示與關聯,例如標籤(label)、描述(description)、各種 ARIA 屬性等等(如 aria-invalid)。
- 瀏覽器插件:使用像 Lighthouse 或 WAVE 這類插件,掃描頁面並檢查無障礙問題。這些工具可以幫我們檢查標籤、描述、ARIA 屬性是否正確實現,還可以查看顏色對比、焦點順序、網站語意結構等。
進階測試:使用螢幕閱讀器
雖然透過以上提到的基本測試方式,已經能揪出大部分的無障礙錯誤,但是像是焦點管理不當、描述性訊息缺乏或錯誤的語意標籤等等問題,就必須透過螢幕閱讀器才能發現。因此,如果要更貼近使用者真實的體驗,建議開發者親自使用螢幕閱讀器測試,才能發現一般檢查中可能忽略的問題。
對於 Windows 使用者,可以使用 NVDA 或 JAWS 來做測試,對於 Mac 使用者,可以使用 VoiceOver。在行動裝置上則可以使用 TalkBack(Android)或 VoiceOver(iOS)來測試。
React 範例
在 Frontend Mentor 網站上的這個練習非常適合實作基礎無障礙表單,因此本篇文章的範例就決定使用此設計稿。有興趣的朋友不妨也到網站下載設計稿並親自實作看看。
建議可以先到 👉Live Demo👈實際使用鍵盤、螢幕閱讀器操作看看,觀察 Devtools 中無障礙面板中的資訊。接著,再到👉Github Repo👈中查看原始碼,以下幾個問題提供大家思考看看:
- Markup 中使用了哪些語意化 HTML 元素?螢幕閱讀器如何報讀這些元素?
- Markup 中使用了哪些 ARIA 標籤?他們有哪些功能?呈現出來的效果是什麼?
- 表單中的元素是否都能使用鍵盤來操作?(Tab, Shift+Tab, Space, Enter, Arrow Keys…)
- 表單中的元素獲得焦點時,是否都有清晰可見的焦點樣式?
- 錯誤發生時,哪些欄位有誤、以及欄位的錯誤訊息是否有正確傳遞?這是如何做到的?
- 成功送出表單後,成功訊息是否有正確傳遞?這是如何做到的?
除了使用語意化 HTML 標籤以外,此範例還搭配了 react-hook-form、zod 這兩個常見的套件來處理表單驗證,其中 react-hook-form 提供了驗證後將焦點移至錯誤欄位與即時驗證的功能,而 zod 則是簡化了 schema 與錯誤訊息設定的流程。
結語:我的網站符合無障礙標章的規定,這樣就夠了嗎?
在初學網頁開發的時候,課程中多少都會帶到一些無障礙基本概念,像是使用正確的 HTML 元素、<img> 要記得加上 alt 替代文字等,但是當了解越來越多無障礙知識、並實際在專案中實作後,就更深刻感受到無障礙不僅僅只是在一系列的標準和規定清單中打勾而已。
前面也有提到,很多 WCAG 中無障礙的重要指標:鍵盤操作、語意標籤完整性等是較難從單純的自動化測試中發現問題的,即便網站在 Google Lighthouse 的 Accessibility 指標是滿分,並不代表網站的無障礙體驗是沒有問題的。這就是為什麼無障礙標章審核除了機器自動審核以外,都還需要人工檢核的原因。
你可能會納悶,無障礙這麼多眉角、這麼麻煩,身為一個平常已經很多鳥事的開發者,到底該怎樣做才對?其實,無障礙本來就不只是開發者單方面的責任,從設計、測試甚至到管理端,整體流程的討論與建立才能為使用者與團隊帶來最大的效益。
在這篇與產品設計師 Oly 的訪談中提到,無障礙不只是技術的挑戰,更牽涉了整個公司組織文化的轉變;如果現實條件並不允許,可以從個人角度出發,像工程師 Jamie 一樣參與無障礙 side project、在公司內部做推廣等,也能為數位無障礙貢獻一份心力。正如科技教育總監 Sally 所強調的,無障礙不需要一開始就做到完美,只要開始著手執行,哪怕只是一點改善,都是向前邁進的一步。