跳到主要內容

無障礙基準:做到這些就贏過九成的網站

鍵盤、焦點、語意標籤、對比、ARIA 的最小必要集合,以及一份十分鐘就能跑完的自我檢測流程。

約 6 分鐘 · accessibility.md

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 改變視覺順序(ordergrid-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. 十分鐘自我檢測

不需要工具,照做就好:

  1. 收起滑鼠,用 Tab 走完整頁。每一步都看得到焦點嗎?走得到所有功能嗎?
  2. 按 Escape。彈窗會關嗎?關了以後焦點回到原處嗎?
  3. 放大到 200%(Ctrl/Cmd +)。版面壞了嗎?有橫向捲動嗎?
  4. DevTools → Rendering → Emulate vision deficiencies。灰階模式下,還分得出狀態嗎?
  5. Lighthouse → Accessibility。90 分以下就有明顯問題。
  6. 開螢幕閱讀器(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% 縮放不破版

顯示設定

這裡改的每一項,會即時套用到站上所有預覽。

風格

密度

圓角

動態

系統層級的「減少動態效果」永遠優先於這裡的設定。

語言