iT邦幫忙

2025 iThome 鐵人賽

DAY 18
2
Modern Web

原生元件養成計畫:Web Component系列 第 18

Day 18: Web Component 應用-為表單元件加入無障礙 Accessibility

  • 分享至 

  • xImage
  •  

今天我們來學點不一樣的吧!

有鑒於我們隊友的太太是愛通用設計的小魚
讓我在實作表單元件的過程中不禁想到,如果表單元件沒辦法被其他輔助工具正確操作的話,那麼他是否就不能被稱為是一個完整的表單元件呢?

在前幾天的學習中,已經學會了關於自訂表單元件的屬性 attribute、值 value、驗證 validation 等方法。
現在,讓我們加入更友善的元件 無障礙 (Accessibility) 功能,讓表單元件可以更通用,更符合不同族群的需求。

什麼是元件的無障礙?

元件無障礙設計的重點,其實是希望可以讓所有使用者都能正確操作元件。
我們要考慮的對象不只是滑鼠的使用者,還需要顧及到使用螢幕閱讀器抑或是只使用鍵盤操作的使用者。
而原生的元件就自帶了無障礙的特性。

預期無障礙的元件應該要具備以下功能:

  • 滑鼠使用者:能夠點擊、輸入。
  • 鍵盤使用者:是否能用鍵盤的按鍵切換焦點、輸入內容、發送...。
  • 螢幕閱讀器使用者:當元件聚焦時,是否能正確朗讀這是什麼元件、標籤、狀態...。

原生元件的無障礙特性

在原生 <input><button> 等 HTML 元素,其實天生就支援很多無障礙特性,像是:

  • 螢幕閱讀器能自動讀出有帶上 label 的元件所綁定的文字。
  • 必填欄位或是驗證失敗,會有聲音或提示。
  • 可以使用鍵盤的 Tab 移動焦點。
  • 可以使用 Enter 或其他按鍵觸發操作。

但你要想想,我們目前在開發的是 <custom-input> 自訂表單元件,它並不具有原生元件的這些能力,所以我們要自己替它加入相關的方法(就像是前面所學到的表單驗證方法或是給值的方法一樣)。

那我們該怎麼加入呢?
這時就需要來聊聊 元件的 ARIA (Accessible Rich Internet Applications) 屬性

元件中常用的 ARIA(Accessible Rich Internet Applications)屬性

講到無障礙元件,你就必須要想到:ARIA
我們要使用 ARIA 告訴瀏覽器,這個自訂元件應該被視為什麼角色,又或是目前的狀態是什麼。

  • role="角色":指定元件的語意角色,告訴輔助工具這個元件應該被視為什麼,像是 role="textbox",代表這是一般文字輸入框。
    • 如果已經使用了語意明確的 HTML 元素,<input type="text">,就不需要再額外加上 role。只有在自訂元件像是我們正在實作的 Web Component 或非語意化元素 <div><span> 等等元素才需要補上。
    • 參考資料:MDN ARIA Roles
  • 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 內的元素大部分螢幕閱讀器不會讀!

  1. 加上屬性 role:更明確表單元件的角色,我們在實作中使用的是 div 結合 contenteditable。螢幕閱讀器會預設這種類型的元件就是文字的編輯器,但為了加強角色的定位,所以我們替他加上:role="textbox"
  2. 加上 aria-required="true":讓螢幕閱讀器知道這個元件是必填。
  3. 加上 aria-label:讓外部可以傳入自定義的標籤名稱供螢幕閱讀器辨別。
  4. 加上 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>

該如何在自己的電腦模擬螢幕閱讀器測試

  1. Mac 使用者: Command (⌘) + F5 開啟旁白
    mac
  2. Windows 使用者: Windows 標誌鍵 + Ctrl + Enter 開啟 Narrator(朗讀程式)
    window

當開啟旁白後,可以聽見閱讀器念出內容

reader

奇怪了,為什麼沒有念出錯誤訊息?

當使用上方的程式碼測試後,會發現:旁白並沒有念出錯誤訊息。
這是我在實驗時遇到的一個問題!

為什麼會這樣呢?
原來是因為螢幕閱讀器會自動讀取動態更新的內容,如果你用普通 <span>,它不會自動讀。
我們需要加上:aria-live="polite",告訴螢幕閱讀器這個區塊的內容可能會隨時更新,請在更新時把新內容唸給使用者聽。
polite 這個屬性代表的是:讀完當前內容後才唸更新,才不會突然打斷正在聽的內容。

試試看把下面這段程式碼補充進上方的 span 吧!

<span id="msg" style="font-size:12px;" aria-live="polite"></span>

你會發現,當按下 check form 按鈕後,閱讀器就幫你念出錯誤訊息了!(請各位先忽略框線跑位的問題,暫時不確定旁白閱讀時為什麼會有跑掉的問題)
error

到這裡,我們的自訂表單元件就具備基本的無障礙支援囉!

小提醒:雖然我們的自訂元件一直聚焦在 Shadow DOM 中,但是要使他成為無障礙元件的話,可讀資訊應該放在 Light DOM(custom element 外層),而不是 Shadow DOM 裡唷!


上一篇
Day 17: Web Component 應用-表單元件的驗證方法
下一篇
Day 19: Web Component 應用-為表單元件加入 TypeScript
系列文
原生元件養成計畫:Web Component21
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言