今天我們來學點不一樣的吧!
有鑒於我們隊友的太太是愛通用設計的小魚。
讓我在實作表單元件的過程中不禁想到,如果表單元件沒辦法被其他輔助工具正確操作的話,那麼他是否就不能被稱為是一個完整的表單元件呢?
在前幾天的學習中,已經學會了關於自訂表單元件的屬性 attribute
、值 value
、驗證 validation
等方法。
現在,讓我們加入更友善的元件 無障礙 (Accessibility)
功能,讓表單元件可以更通用,更符合不同族群的需求。
元件無障礙設計的重點,其實是希望可以讓所有使用者都能正確操作元件。
我們要考慮的對象不只是滑鼠的使用者,還需要顧及到使用螢幕閱讀器抑或是只使用鍵盤操作的使用者。
而原生的元件就自帶了無障礙的特性。
預期無障礙的元件應該要具備以下功能:
在原生 <input>
、<button>
等 HTML 元素,其實天生就支援很多無障礙特性,像是:
label
的元件所綁定的文字。但你要想想,我們目前在開發的是
<custom-input>
自訂表單元件,它並不具有原生元件的這些能力,所以我們要自己替它加入相關的方法(就像是前面所學到的表單驗證方法或是給值的方法一樣)。
那我們該怎麼加入呢?
這時就需要來聊聊 元件的 ARIA (Accessible Rich Internet Applications) 屬性
!
講到無障礙元件,你就必須要想到:ARIA
。
我們要使用 ARIA
告訴瀏覽器,這個自訂元件應該被視為什麼角色,又或是目前的狀態是什麼。
role="角色"
:指定元件的語意角色,告訴輔助工具這個元件應該被視為什麼,像是 role="textbox"
,代表這是一般文字輸入框。
<input type="text">
,就不需要再額外加上 role。只有在自訂元件像是我們正在實作的 Web Component 或非語意化元素 <div>
、<span>
等等元素才需要補上。aria-label="標籤文字"
:提供一個可讀的標籤,適合用在元件本身沒有 <label>
的情況。像是一個自訂搜尋框可以替它加上 aria-label="搜尋"
。aria-required="true"
:標示此欄位為必填,輔助工具就會告訴使用者他是必填。aria-invalid="true/false"
:標示欄位是否驗證失敗,如果是 true
就代表驗證失敗。aria-describedby="id"
:告訴輔助工具這個元素有一段描述文字說明,說明內容會被放在對應 id 的元素中。ARIA 有個大原則叫做 "Don't use ARIA if you can use HTML"。
如果能用原生 HTML 的就用原生,不要一開始就套 aria,因為原生支援度和一致性通常比自己手動加 aria 更好!
注意:ARIA 屬性必須放在 Light DOM 的 custom element 上,Shadow DOM 內的元素大部分螢幕閱讀器不會讀!
role
:更明確表單元件的角色,我們在實作中使用的是 div
結合 contenteditable
。螢幕閱讀器會預設這種類型的元件就是文字的編輯器,但為了加強角色的定位,所以我們替他加上:role="textbox"
。aria-required="true"
:讓螢幕閱讀器知道這個元件是必填。aria-label
:讓外部可以傳入自定義的標籤名稱供螢幕閱讀器辨別。aria-describedby="msg"
:告訴輔助工具這個元素有一段描述文字說明。input.js
先補一個公開方法:
// 公開 errorMessage
public getErrorMsg() {
return this._internals.validationMessage;
}
index.html
<body>
<form style="display: flex; flex-direction: column; gap: 8px">
<custom-input
name="content"
value="defaultValue"
required
max-length="5"
min-length="2"
role="textbox"
aria-required="true"
aria-label="這是一個輸入框"
aria-describedby="msg"
>
</custom-input>
<span id="msg" style="font-size: 12px;"></span>
<input name="second" type="text" required>
<button type="button" id="button">check form</button>
</form>
<script src="input.js"></script>
<script>
const checkButton = document.getElementById('button');
const customInput = document.querySelector('custom-input');
const msgElement = document.getElementById('msg');
// 在 value change 的同時檢查自訂元件的驗證狀態
customInput.addEventListener('value-changed', e => {
customInput.reportValidity();
});
checkButton.addEventListener('click', e => {
const isValid = customInput.checkValidity();
if (!isValid) {
customInput.setAttribute('aria-invalid', 'true');
msgElement.textContent = customInput.getErrorMsg();
} else {
customInput.removeAttribute('aria-invalid');
msgElement.textContent = '';
}
});
</script>
</body>
Command (⌘) + F5
開啟旁白Windows 標誌鍵 + Ctrl + Enter
開啟 Narrator(朗讀程式)當使用上方的程式碼測試後,會發現:旁白並沒有念出錯誤訊息。
這是我在實驗時遇到的一個問題!
為什麼會這樣呢?
原來是因為螢幕閱讀器會自動讀取動態更新的內容,如果你用普通 <span>
,它不會自動讀。
我們需要加上:aria-live="polite"
,告訴螢幕閱讀器這個區塊的內容可能會隨時更新,請在更新時把新內容唸給使用者聽。polite
這個屬性代表的是:讀完當前內容後才唸更新,才不會突然打斷正在聽的內容。
試試看把下面這段程式碼補充進上方的 span 吧!
<span id="msg" style="font-size:12px;" aria-live="polite"></span>
你會發現,當按下 check form 按鈕後,閱讀器就幫你念出錯誤訊息了!(請各位先忽略框線跑位的問題,暫時不確定旁白閱讀時為什麼會有跑掉的問題)
到這裡,我們的自訂表單元件就具備基本的無障礙支援囉!
小提醒:雖然我們的自訂元件一直聚焦在 Shadow DOM 中,但是要使他成為無障礙元件的話,可讀資訊應該放在 Light DOM(custom element 外層),而不是 Shadow DOM 裡唷!