先說一個很常見的錯誤
<!-- 你在 code review 裡很可能看過這種寫法 -->
<div role="button" aria-label="送出" onclick="submit()">送出</div>這個 div 加了 ARIA role,理論上螢幕閱讀器會把它讀成按鈕。但它沒有:
- 原生的鍵盤聚焦(除非加
tabindex="0") - 原生的 Enter / Space 鍵觸發
- 原生的 disabled 狀態
- 行動裝置原生按鈕的 touch target 優化
用了 ARIA 卻沒補全這些,比完全不用 ARIA 還糟——螢幕閱讀器以為這是按鈕,但鍵盤使用者按 Enter 什麼都不發生。
W3C ARIA 規格的第一條規則就是:能用原生 HTML element 或 attribute 達到語意,就用原生的。
原生 HTML 能做到的,不要用 ARIA 重做
這是最重要的判斷點。
<!-- ❌ 用 ARIA 重做原生語意 -->
<div role="button">點我</div>
<div role="checkbox" aria-checked="true">同意條款</div>
<div role="navigation">...</div>
<div role="heading" aria-level="2">標題</div>
<!-- ✅ 直接用原生元素 -->
<button>點我</button>
<input type="checkbox" checked> 同意條款
<nav>...</nav>
<h2>標題</h2>原生元素的好處不只是少寫 ARIA——瀏覽器幫你處理了焦點管理、鍵盤互動、表單提交、狀態同步這些事情。用 div 加 ARIA role 重做,你要自己補全這一切。
真正需要 ARIA 的四個場景
1. 自訂互動元件沒有原生 HTML 對應
有些 UI 模式 HTML 沒有原生元素:tabs、combobox、tree view、slider、dialog(2024 前)。
<!-- Tab 介面沒有原生 HTML,必須用 ARIA -->
<div role="tablist">
<button role="tab" aria-selected="true" aria-controls="panel-1">Tab 1</button>
<button role="tab" aria-selected="false" aria-controls="panel-2">Tab 2</button>
</div>
<div role="tabpanel" id="panel-1">...</div>
<div role="tabpanel" id="panel-2" hidden>...</div>注意:<dialog> 元素現在已經有原生支援,2024 年後可以直接用,不需要 role="dialog"。
2. 動態內容更新需要通知螢幕閱讀器
當畫面局部更新(AJAX 結果、表單驗證錯誤、通知訊息),螢幕閱讀器不會自動知道發生了什麼。
<!-- 表單驗證錯誤即時出現時 -->
<span role="alert">Email 格式不正確</span>
<!-- 非緊急的狀態更新(例如「已儲存」) -->
<div aria-live="polite" aria-atomic="true">
<span>草稿已儲存</span>
</div>role="alert" 會立刻打斷並朗讀;aria-live="polite" 會等使用者停頓後再讀。選哪個取決於是否緊急。
3. 視覺上有關聯但 HTML 結構無法表達
<!-- 圖示按鈕沒有文字,需要 aria-label -->
<button aria-label="關閉對話框">
<svg>...</svg>
</button>
<!-- 表格的行/欄標題跨越多格 -->
<th scope="colgroup" colspan="2">第一季</th>
<!-- 一個標籤對應多個輸入欄位(如日期分三格) -->
<fieldset>
<legend>出生年月日</legend>
<input aria-label="年" type="number">
<input aria-label="月" type="number">
<input aria-label="日" type="number">
</fieldset>4. 元素的狀態或屬性無法從 HTML 結構看出來
<!-- 展開/收合狀態 -->
<button aria-expanded="false" aria-controls="submenu">選單</button>
<ul id="submenu" hidden>...</ul>
<!-- 目前選中的頁面(breadcrumb / pagination) -->
<nav aria-label="分頁">
<a href="/page/1">1</a>
<a href="/page/2" aria-current="page">2</a>
<a href="/page/3">3</a>
</nav>
<!-- 必填欄位(不用 required 的情境,例如 custom input) -->
<div role="textbox" aria-required="true" contenteditable>...</div>role vs 原生元素的邊界
一個簡單的判斷流程:
HTML 有對應的原生元素嗎?
├── 有 → 用原生元素(結束,不需要 ARIA role)
└── 沒有
└── 這個元件有動態狀態需要告知使用者嗎?
├── 有 → 選對應的 ARIA role + 所有必要的 aria-* 屬性
└── 沒有 → 考慮是否純視覺用途,純視覺用 aria-hidden="true"
三個最常見的 ARIA 誤用
誤用一:aria-label 蓋掉有意義的文字
<!-- ❌ aria-label 會完全蓋掉 <a> 裡的文字,讀出來是「更多資訊」而非「深入了解 TypeScript」 -->
<a href="/typescript" aria-label="更多資訊">深入了解 TypeScript</a>
<!-- ✅ 連結文字本身就有語意,不需要 aria-label -->
<a href="/typescript">深入了解 TypeScript</a>誤用二:aria-hidden=“true” 把鍵盤可聚焦元素藏起來
<!-- ❌ 視覺上看不到,但鍵盤仍可聚焦,造成「幽靈焦點」 -->
<div aria-hidden="true">
<button>我是隱藏的按鈕但鍵盤還是能焦點到我</button>
</div>
<!-- ✅ 連同 tabindex 或 disabled 一起處理 -->
<div aria-hidden="true" inert>
<button>現在鍵盤也進不來</button>
</div>誤用三:每個 icon 都加 aria-label,但 icon 旁邊本來就有文字
<!-- ❌ 有文字的情況下 icon 不需要 aria-label,會讀兩次 -->
<button>
<svg aria-label="儲存">...</svg>
儲存
</button>
<!-- ✅ icon 純裝飾,用 aria-hidden 讓螢幕閱讀器忽略它 -->
<button>
<svg aria-hidden="true" focusable="false">...</svg>
儲存
</button>相關文章
- HTML 語意化標籤:結構六大標籤與判斷方法 — 原生語意標籤的基礎
- 為什麼不能只用 div — 語意化的根本理由
- 那些你知道存在但一直沒用的 HTML 標籤 — 原生語意的延伸
- CH12 無障礙設計 — ARIA 的完整用法與 a11y 深入