無障礙基準:做到這些就贏過九成的網站
鍵盤、焦點、語意標籤、對比、ARIA 的最小必要集合,以及一份十分鐘就能跑完的自我檢測流程。
0. 心態
無障礙不是「幫少數人做的額外功能」。鍵盤操作、清楚的焦點、足夠的對比、可縮放的文字,是所有人在某些情境下都需要的東西 —— 手上拿著東西、在陽光下看螢幕、滑鼠壞了、四十歲之後。
1. 用對的標籤(投報率最高)
| 要做的事 | 用 | 不要用 |
|---|---|---|
| 可點擊的動作 | <button> |
<div onclick> |
| 前往別的頁面 | <a href> |
<button onclick={goto}> |
| 彈窗 | <dialog> + showModal() |
自刻的 div |
| 摺疊 | <details> / <summary> |
自刻的 div |
| 表單 | <form> + <label> |
一堆 div 加一顆按鈕 |
| 導覽 | <nav> |
<div class="nav"> |
| 主要內容 | <main> |
<div id="content"> |
原生元素自帶鍵盤支援、焦點管理、螢幕閱讀器語意、以及作業系統層級的行為。自刻等於要重新實作全部,而且一定會漏。
<div onclick> 缺少的東西:無法 Tab 到、Enter/Space 沒反應、螢幕閱讀器不知道它可以點、沒有 disabled 狀態、沒有 focus 樣式。
2. 焦點
一定要看得見
/* 一次設定,全站通用 */
:focus-visible {
outline: none;
box-shadow: var(--ds-ring);
}
永遠不要 outline: none 而不補上替代品。 這是無障礙第一大罪。
用 :focus-visible 而不是 :focus:前者只在鍵盤操作時出現,滑鼠點擊不會留下一圈框(這正是當初大家想拿掉 outline 的原因)。
Tab 順序 = DOM 順序
不要用 tabindex="1" 這種正數,它會把元素排到所有東西前面,製造混亂。只用兩個值:
tabindex="0"— 把非互動元素加入 Tab 序列(很少需要)tabindex="-1"— 可以用程式聚焦,但不在 Tab 序列裡
用 CSS 改變視覺順序(order、grid-area)會讓視覺順序跟 Tab 順序不一致 —— 這是隱藏的無障礙 bug。
焦點要有進有回
開彈窗 → 焦點進去;關彈窗 → 焦點回到原本那顆按鈕。沒有回去的話,鍵盤使用者會被丟回頁面最上方。
跳過導覽
每一頁的第一個 Tab 停留點應該是「跳到主要內容」:
<a class="ui-skip" href="#main">跳到主要內容</a>
.ui-skip { position: fixed; top: 8px; left: 8px; transform: translateY(-200%); }
.ui-skip:focus { transform: translateY(0); }
3. 對比
| 內容 | 最低 |
|---|---|
| 一般文字 | 4.5:1 |
| 大字(≥24px,或 ≥18.66px 粗體) | 3:1 |
| UI 元件邊界、圖示 | 3:1 |
| 停用狀態 | 無要求(但別讓人看不出是停用) |
品牌色當文字幾乎都不合格。 亮綠、黃、青在淺底上都是 1–2:1。這就是為什麼設計系統要分 --ds-accent(底色用)與 --ds-accent-ink(文字用)。
每個風格的設計系統頁上都有對比表,可以直接看。
顏色不可以是唯一的資訊管道。 紅色代表錯誤時,旁邊要有文字或圖示 —— 約 8% 的男性有色覺差異。
4. 圖片與圖示
<img src="chart.png" alt="營收從 1 月的 120 萬成長到 6 月的 310 萬" />
<img src="decoration.svg" alt="" />
<button aria-label="關閉"><span aria-hidden="true">✕</span></button>
- 有資訊的圖:描述資訊,不是描述畫面
- 裝飾圖:
alt=""(空字串,不是省略) - 圖示按鈕:一定要
aria-label,裡面的符號aria-hidden="true"
5. ARIA 的第一守則
能用原生就不要用 ARIA。
錯誤的 ARIA 比沒有 ARIA 更糟 —— 它會覆蓋掉原本正確的語意。
真正常用的只有幾個:
<!-- 目前所在頁 -->
<a href="/modules" aria-current="page">模組</a>
<!-- 展開狀態 -->
<button aria-expanded="false" aria-controls="menu">選單</button>
<!-- 開關 -->
<button role="switch" aria-checked="true" aria-label="減少動態效果"></button>
<!-- 表單錯誤 -->
<input aria-invalid="true" aria-describedby="email-error" />
<!-- 動態訊息 -->
<div aria-live="polite">已儲存</div>
<div role="alert">上傳失敗</div>
aria-live="polite" 等使用者講完再說;role="alert"(等同 assertive)會打斷。錯誤用 alert,狀態用 polite。
6. 動態內容
- 內容更新要透過 live region 宣告,否則螢幕閱讀器使用者不知道發生了什麼
- 載入中要有
aria-busy="true"或一個 live region - 無限捲動要有「載入更多」按鈕當替代路徑 —— 純捲動觸發對鍵盤使用者不友善
7. 動態效果
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 1ms !important;
transition-duration: 1ms !important;
scroll-behavior: auto !important;
}
}
視差捲動與大幅位移會讓有前庭障礙的人不適。這不是喜好問題。
8. 十分鐘自我檢測
不需要工具,照做就好:
- 收起滑鼠,用 Tab 走完整頁。每一步都看得到焦點嗎?走得到所有功能嗎?
- 按 Escape。彈窗會關嗎?關了以後焦點回到原處嗎?
- 放大到 200%(Ctrl/Cmd +)。版面壞了嗎?有橫向捲動嗎?
- DevTools → Rendering → Emulate vision deficiencies。灰階模式下,還分得出狀態嗎?
- Lighthouse → Accessibility。90 分以下就有明顯問題。
- 開螢幕閱讀器(Mac: Cmd+F5 / Windows: NVDA)唸一次首頁。聽得懂嗎?
第 1 項就能抓到大部分問題。
9. 檢查清單
- 用原生元素,不自刻按鈕與彈窗
- 每個互動元素 focus 可見
- 沒有
outline: none而不補替代 - Tab 順序 = 視覺順序
- 有跳過導覽連結
- 彈窗焦點有進有回
- 文字對比 ≥ 4.5:1
- 顏色不是唯一資訊管道
- 圖片有正確的 alt,圖示按鈕有 aria-label
- 表單欄位有 label 與 aria-describedby
- 動態訊息有 live region
- 有 prefers-reduced-motion 處理
- 200% 縮放不破版