實作無障礙網頁功能:今天沒有拖棚之表單 radio,要選就只選我吧! – iT 邦幫忙::一起幫忙解決難題,拯救 IT 人的一天

這系列無障礙的鐵人賽文章,實踐的內容主要是根據 W3C:WAI-ARIA 的實踐,從設計模式及組件(Design Patterns and Widgets)裡面挑選最想嘗試的,如果有朋友想瞭解全部 Widget 該怎麼實作及其規範,歡迎自行爬規範內容,也許我們可以討論一下;若以下文章內容理解有任何錯誤,請多指教~

單選的 Radio(打開文件)

3.17 Radio Group
A radio group is a set of checkable buttons, known as radio buttons, where no more than one of the buttons can be checked at a time. Some implementations may initialize the set with all buttons in the unchecked state in order to force the user to check one of the buttons before moving past a certain point in the workflow.

回想到剛開始學習網頁的時候,常常把「checkbox」和「radio」搞不清楚,這兩個在原生表單元素中都是很常使用到的。到底為什麼 radio 要叫做 radio 呢?

Radio 名稱的由來

(圖片來源:MDN)

因為 radio 的設計來說,不論外觀或是功能,都和早期的收音機上的按鈕非常相似,一次只能選擇一個功能,比如說上圖的 LW,那麼點按其他功能 BC-A時, LW 就會跳起來,只運作你最後選擇的 BC-A,也就是「單選」的意思。

複習原生的 radio 該怎麼寫

我們來看一下原生的表單元素 radio 的寫法:

<input type="radio" id="ramen" name="japanese-food" value="ramen" checked>
  • 這個元素是 <input> 標籤,類型是 radio
  • 它有一個獨特的識別 id ,值是 remen(拉麵)。
  • namechecked 非常重要,之後會舉例說明。
  • value 是當前那個 radio 的值,是我們會傳給後端的內容,而它並不會出現在 UI 畫面上,如下圖左邊的呈現,使用者點的時候並不會知道他點了什麼東西(黑人問號???),下圖右邊的畫面才是真正使用者比較適合看到的內容,知其所以。

Q:那麼我們怎麼做,才可以呈現右邊的文字補充呢?

透過 <label> 來加上標籤,必須(握拳!)

原生寫法會透過 <label> 來為每個表單元素加上標註,螢幕閱讀器也能念出到每個 input 的「標題」,加上標題後,當我們在表單中點擊了「那個標題」,就等於在點擊以建立關聯性的 input。如果你已經知道的話,那麼你知道建立關聯性有兩種做法嗎?

  1. <label><input> 拆開來寫,用 for 屬性指向 inputid

  2. <label> 當作容器把 <input> 包起來,不用使用 forid 囉!因為連結性已隱含在其中。

如果想了解更多 label 的無障礙做法,可以來看 WAI 的教學,這裡不再贅述。

那麼來說明一下 name 與 checked 係蝦咪?

你今天到一間日式料理餐廳吃飯

(圖片來源:Unsplash)

今天想點套餐來吃個!

主餐的部分要從 Menu 中選出一項主食,假設你喜歡吃拉麵好了,拉麵區有「醬油拉麵、蔥燒拉麵、海鮮拉麵….等」,到底要吃哪個比較好呢?

那麼這樣的情境在 radio 的呈現就會是一組的選項,我們會將它叫做「Radio Group」。

原始碼長這樣:

<!-- 三個 radio 組成的 radio group -->
<label>
    <input type="radio" name="main-dish" value="1" checked>
    <p>醬油拉麵</p>
</label>

<label>
    <input type="radio" name="main-dish" value="2" checked>
    <p>蔥燒拉麵</p>
</label>

<label>
    <input type="radio" name="main-dish" value="3" checked>
    <p>海鮮拉麵</p>
</label>
  • 這是由三個 radio 組成的 group,原因是它們的 name 寫的值都是同一個欄位 main-dish
    • 白話文:菜單上主食的拉麵區,寫著「醬油拉麵」、「蔥燒拉麵」、「海鮮拉麵」。
    • ⇒ 拉麵區(Radio Gruup)、主食(name="main-dish")。
  • checked 是可加、可不加的狀態,在一個 radio group 中的 radio 加上 checked 表示這組三個選項當中,我們現在單選的是它。

    • 白話文:選了「醬油拉麵」,就不能同時選擇另一個「蔥燒拉麵」了!
  • value 是我們跟後端溝通好要儲存的值,和我們顯示的 UI 名稱不一定要一樣。

    • 白話文:選了「醬油拉麵」,main-dish 這個欄位會傳值 1 給後端,而「醬油拉麵」只是個給使用者標題。

下一個 Radio 的結論

在實際表單運用中,通常是一整組的,我們稱為 Radio Group。而 Radio Group 是由多個 radio 組成,一組只會有一個 radiochecked 的狀態。

上面介紹完 radio 的定義及原生的作法,以下內容要說明 Radio Group 作為 WAI 中的設計模式之一,在無障礙中有什麼要注意的地方?如果今天要自己刻一個同性質的做法,也就是使用非原生 input ,那麼一定要特別特別注意無障礙的實現。

鍵盤的可訪問性

  • Radio Group 成為焦點時,組內有任何 radio 是處於 checked 的狀態嗎?
    • 有: focus 在該 radio 上。
    • 沒有: focus 在組內第一個 radio 上。
  • Space 鍵
    • 可以切換 radio 的選取狀態( checked )。
  • Right、Down 方向鍵
    • 可以移動焦點到「下一個 radio 」 ,如果是最後一個,就移動到組中「第一個 radio」,並選中新焦點的 radio
  • Left、Up 方向鍵
    • 可以移動焦點到「上一個 radio 」 ,如果是第一個,就移動到組中「最後一個 radio」,並選中新焦點的 radio

Roles、States、Properties

  • Radio Group 的容器,要設定角色 role,值為 radiogroup
  • 如果 radio 不是原生的 input,那麼我們 radio 需要加上角色 role="radio"
  • 每個 radio 都需要加上 aria-checked,值是 truefalse
  • 當你給 radio 加上角色 role="radio" 之後,每個 radio 會以本身中的文字內容作為「名稱」標示(label),不過也可以使用 aria-labelledby="某元素id" 或是 aria-label="醬油拉麵"來補充語義。
  • Radio Group 的容器,同樣可以使用aria-labelledby="某元素id" 或是 aria-label="主餐"來補充語義。

實踐開始囉!

今天一樣引用 WAI-ARIA Practice 1.1 的範例 Radio Group Example Using aria-activedescendant,改成我們自己想要的情境吧!

好,你剛剛去日式料理餐廳用餐完畢之後,服務人員想請你填寫「服務滿意度調查」,像這樣的介面的無障礙該怎麼實現呢?

希望能做到:

  • [x] 支援螢幕閱讀器
  • [x] 可使用鍵盤操作
<!-- Radio Gruop 的標題 -->
<h3 id="group_label_1">
    環境舒適嗎?
</h3>
<!-- Radio Gruop 的主體 -->
<ul id="rg1"
    class="radiogroup"
    role="radiogroup"
    aria-labelledby="group_label_1"
    aria-activedescendant="rb11"
    tabindex="0">
      <li id="rb11"
          role="radio"
          aria-label="極好"
          aria-checked="false">
      </li>
      <li id="rb12"
          role="radio"
          aria-label="還行"
          aria-checked="false">
      </li>
      <li id="rb13"
          role="radio"
          aria-label="普通"
          aria-checked="false">
      </li>
      <li id="rb14"
         role="radio"
         aria-label="不喜歡"
         aria-checked="false">
      </li>
      <li id="rb15"
         role="radio"
         aria-label="討厭"
         aria-checked="false">
      </li>
</ul>
  • 使用 <ul> 作為 Radio Group 的主體, <li>radio
  • <ul>:Radio Group 的主體!

    • 使用 role="radiogroup",定義角色是 radio group。
    • aria-labelledby 填入標題的元素 id,上面例子當中就是那個 <h3> 標籤。
    • 透過 JavaScript,動態更換 aria-activedescendant 中的值,代表這整個 Radio Group 選中誰。
    • 設定 tabindex="0",讓鍵盤可以對一個非操控元素 <ul> 產生焦點(focusable)。
  • <li>:每一個 li 都當作 radio。

    • 設定 role="radio" ,定義角色是 radio
    • id 的值,是我們要傳給 <ul> 中的 aria-activedescendant 屬性的值。
    • aria-label 是我們賦予的標題,螢幕閱讀器會唸出這個內容給使用者聽,他們將會知道目前的 radio 是什麼選項內容。
    • 透過 aria-checked 可以設定現在是否被選擇,記得,同一組 Radio Gruop 中,只會有一個選項被選取,是單選的唷!

花了我九牛二虎之力,跟設計圖一樣的完整程式碼 => codepen 在這裏,快來使用鍵盤操作看看吧!

Reference

Alternate Text Gọi ngay