無障礙性 (a11y)
基於 WCAG 2.2 和 Lighthouse 無障礙稽核的全面無障礙指南。目標:讓每個人都能使用內容,包括身障人士。
基於證據的稽核工作流程
當有已渲染的頁面時:
- 在該功能可用時,執行即時 Lighthouse 無障礙稽核;使用 Chrome DevTools MCP 時,請使用
lighthouse_audit。對於一般公開頁面,使用行動導航模式;當重新載入會遺失已驗證或用戶建立的狀態時,使用快照模式。 - 使用失敗的稽核節點來定位相關元件或模板,而不是在整個儲存庫中搜尋通用模式。
- 檢查已渲染的無障礙樹快照,以取得名稱、角色、狀態、地標和標題結構;使用 Chrome DevTools MCP 時,請使用
take_snapshot。使用鍵盤操作受影響的流程。 - 修正來源,然後重新執行相同的稽核和手動互動。
如果 Live 工具不可用,請使用 Lighthouse CLI 或 axe 進行自動化覆蓋,並完成相同的⼿動檢查。自動化工具只能偵測一部分的無障礙障礙:滿分 100 並不等於 WCAG 合規,低分數也不能取代基於問題的證據。
WCAG 原則:POUR
| 原則 | 說明 |
|---|---|
| P 可感知 (Perceivable) | 內容可以透過不同的感官來感知 |
| O 可操作 (Operable) | 介面可供所有使用者操作 |
| U 可理解 (Understandable) | 內容和介面是可理解的 |
| R 穩健 (Robust) | 內容可與輔助技術相容使用 |
符合性等級
| 等級 | 要求 | 目標 |
|---|---|---|
| A | 最低無障礙性 | 必須通過 |
| AA | 標準合規 | 應該通過(在許多司法管轄區是法律要求) |
| AAA | 增強無障礙性 | 有則更好 |
可感知 (Perceivable)
文字替代方案 (1.1)
圖片需要替代文字:
<!-- ❌ 缺少 alt -->
<img src="chart.png">
<!-- ✅ 描述性 alt -->
<img src="chart.png" alt="長條圖顯示第三季銷售額成長 40%">
<!-- ✅ 裝飾性圖片(空的 alt) -->
<img src="decorative-border.png" alt="" role="presentation">
<!-- ✅ 複雜圖片配有更長的說明 -->
<figure>
<img src="infographic.png" alt="2024 市場趨勢資訊圖表"
aria-describedby="infographic-desc">
<figcaption id="infographic-desc">
<!-- 詳細說明 -->
</figcaption>
</figure>
圖示按鈕需要可存取的名稱:
<!-- ❌ 沒有可存取的名稱 -->
<button><svg><!-- 選單圖示 --></svg></button>
<!-- ✅ 使用 aria-label -->
<button aria-label="開啟選單">
<svg aria-hidden="true"><!-- 選單圖示 --></svg>
</button>
<!-- ✅ 使用視覺上隱藏的文字 -->
<button>
<svg aria-hidden="true"><!-- 選單圖示 --></svg>
<span class="visually-hidden">開啟選單</span>
</button>
視覺上隱藏的類別:
.visually-hidden {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
色彩對比 (1.4.3, 1.4.6)
| 文字大小 | AA 最低要求 | AAA 增強要求 |
|---|---|---|
| 一般文字 (< 18px / < 14px 粗體) | 4.5:1 | 7:1 |
| 大型文字 (≥ 18px / ≥ 14px 粗體) | 3:1 | 4.5:1 |
| UI 元件與圖形 | 3:1 | 3:1 |
/* ❌ 對比度不足 (2.5:1) */
.low-contrast {
color: #999;
background: #fff;
}
/* ✅ 足夠的對比度 (7:1) */
.high-contrast {
color: #333;
background: #fff;
}
/* ✅ 焦點狀態也需要對比度 (相對於背景為 3:1,WCAG 1.4.11) */
:focus-visible {
outline: 2px solid currentColor;
outline-offset: 2px;
}
不要僅依靠顏色:
<!-- ❌ 僅用顏色指示錯誤 -->
<input class="error-border">
<style>.error-border { border-color: red; }</style>
<!-- ✅ 顏色 + 圖示 + 文字 -->
<div class="field-error">
<input aria-invalid="true" aria-describedby="email-error">
<span id="email-error" class="error-message">
<svg aria-hidden="true"><!-- 錯誤圖示 --></svg>
請輸入有效的電子郵件地址
</span>
</div>
媒體替代方案 (1.2)
<!-- 帶有字幕的影片 -->
<video controls>
<source src="video.mp4" type="video/mp4">
<track kind="captions" src="captions.vtt" srclang="en" label="English" default>
<track kind="descriptions" src="descriptions.vtt" srclang="en" label="Descriptions">
</video>
<!-- 帶有逐字稿的音訊 -->
<audio controls>
<source src="podcast.mp3" type="audio/mp3">
</audio>
<details>
<summary>逐字稿</summary>
<p>完整的逐字稿文字...</p>
</details>
可操作 (Operable)
鍵盤可存取 (2.1)
所有功能必須可透過鍵盤存取。 優先使用原生的互動元素 — <button>、<a href> 和表單控制項會自動處理 Enter/Space 觸發、焦點和輔助技術語義。只有在無法使用原生元素時,才需要加入⼿動鍵盤處理。
<!-- ❌ 非互動元素僅有點擊:不可聚焦,無鍵盤觸發 -->
<div class="card" onclick="handleAction()">開啟</div>
<!-- ✅ 最佳做法:使用原生按鈕 -->
<button type="button" onclick="handleAction()">開啟</button>
// ✅ 當你「必須」使用非互動元素時(例如帶有 role="button" 的 div),
// 使其可聚焦並處理鍵盤觸發。不要在原生 <button> 上執行此操作 —
// Enter/Space 已經會觸發 click,這樣會導致雙重觸發。
element.setAttribute('role', 'button');
element.setAttribute('tabindex', '0');
element.addEventListener('click', handleAction);
element.addEventListener('keydown', (e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
handleAction();
}
});
沒有鍵盤陷阱。 使用者必須能夠使用 Tab 鍵進入和退出每個元件。對於對話框,請使用模態焦點陷阱模式—原生的 <dialog> 元素會自動處理此問題。
焦點可見 (2.4.7)
/* ❌ 永遠不要移除焦點外框 */
*:focus { outline: none; }
/* ✅ 使用 :focus-visible 來實現僅限鍵盤的焦點 */
:focus {
outline: none;
}
:focus-visible {
outline: 2px solid currentColor; /* 繼承文字顏色 → 已經過對比度檢查 */
outline-offset: 2px;
}
/* ✅ 或者選擇品牌顏色,並驗證其與所有著陸背景的對比度 ≥3:1 */
button:focus-visible {
box-shadow: 0 0 0 3px rgba(0, 95, 204, 0.5);
}
焦點未被遮蔽 (2.4.11) — WCAG 2.2 新增
當元素收到鍵盤焦點時,不能完全被其他作者建立的內容(例如固定頁首、頁尾或重疊的面板)隱藏。在 AAA 等級 (2.4.12) 中,焦點元素的任何部分都不得被隱藏。
/* ✅ 在捲動到焦點元素時,考慮固定頁首的影響 */
:target {
scroll-margin-top: 80px;
}
/* ✅ 確保焦點項目避開固定/黏性工具列 */
:focus {
scroll-margin-top: 80px;
scroll-margin-bottom: 60px;
}
跳轉連結 (2.4.1)
提供跳轉連結,讓鍵盤使用者可以跳過重複的導航。有關完整的標記和樣式,請參閱跳轉連結模式。
目標尺寸 (2.5.8) — WCAG 2.2 新增
互動目標的尺寸必須至少為 24 × 24 CSS 像素 (AA)。例外情況:內聯文字連結、瀏覽器控制大小的元素,以及以邊界框為中心的 24px 圓形未與另一個目標重疊的目標。
/* ✅ 最小目標尺寸 */
button,
[role="button"],
input[type="checkbox"] + label,
input[type="radio"] + label {
min-width: 24px;
min-height: 24px;
}
/* ✅ 舒適的目標尺寸(建議 44×44) */
.touch-target {
min-width: 44px;
min-height: 44px;
display: inline-flex;
align-items: center;
justify-content: center;
}
拖曳動作 (2.5.7) — WCAG 2.2 新增
任何需要拖曳的動作都必須有單一指標的替代方案(例如按鈕、輸入)。有關可排序清單的範例,請參閱拖曳動作模式。
時間限制 (2.2)
// 允許使用者延長時間限制
function showSessionWarning() {
const modal = createModal({
title: '工作階段即將到期',
content: '您的工作階段將在 2 分鐘後到期。',
actions: [
{ label: '延長工作階段', action: extendSession },
{ label: '登出', action: logout }
],
timeout: 120000
});
}
動作 (2.3)
/* 尊重減少動作的偏好 */
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
可理解 (Understandable)
頁面語言 (3.1.1)
<!-- ❌ 未指定語言 -->
<html>
<!-- ✅ 已指定語言 -->
<html lang="en">
<!-- ✅ 頁面內的語言變更 -->
<p>法語中的「你好」是 <span lang="fr">bonjour</span>。</p>
一致的導航 (3.2.3)
<!-- 導航應在各頁面間保持一致 -->
<nav aria-label="Main">
<ul>
<li><a href="/" aria-current="page">首頁</a></li>
<li><a href="/products">產品</a></li>
<li><a href="/about">關於</a></li>
</ul>
</nav>
一致的說明 (3.2.6) — WCAG 2.2 新增
如果某個說明機制(聯絡資訊、聊天小工具、常見問題連結、自助選項)在多個頁面上重複出現,則每次必須以相同的相對順序出現。依賴一致位置的用戶不應該在每個頁面上搜尋協助。
表單標籤 (3.3.2)
每個輸入都需要一個程式碼關聯的標籤。有關明確、隱含和指示性範例,請參閱表單標籤模式。
錯誤處理 (3.3.1, 3.3.3)
使用 role="alert" 或 aria-live 向螢幕閱讀器宣布錯誤,在無效欄位上設定 aria-invalid="true",並在提交時將焦點放在第一個錯誤上。有關完整的標記和 JS,請參閱錯誤處理模式。
冗餘輸入 (3.3.7) — WCAG 2.2 新增
不要強迫使用者重新輸入他們在同一工作階段中已提供的資訊。從先前的步驟自動填入,或讓使用者從先前輸入的值中選擇。例外情況:安全重新確認和已過期的內容。
<!-- ✅ 從帳單地址自動填入送貨地址 -->
<fieldset>
<legend>送貨地址</legend>
<label>
<input type="checkbox" id="same-as-billing" checked>
與帳單地址相同
</label>
<!-- 勾選時自動填入欄位 -->
</fieldset>
可存取的驗證 (3.3.8) — WCAG 2.2 新增
登入流程不得依賴認知功能測試(例如記住密碼、解決謎題),除非至少符合以下條件之一:
- 提供複製貼上或自動填入機制
- 存在替代方法(例如通行金鑰、SSO、電子郵件連結)
- 測試使用物體辨識或個人內容(僅 AA;AAA 移除此例外)
<!-- ✅ 允許在密碼欄位貼上 -->
<input type="password" id="password" autocomplete="current-password">
<!-- ✅ 提供無密碼替代方案 -->
<button type="button">使用通行金鑰登入</button>
<button type="button">寄送登入連結給我</button>
穩健 (Robust)
ARIA 使用 (4.1.2)
優先使用原生元素:
<!-- ❌ 在 div 上使用 ARIA 角色 -->
<div role="button" tabindex="0">點擊我</div>
<!-- ✅ 原生按鈕 -->
<button>點擊我</button>
<!-- ❌ ARIA 核取方塊 -->
<div role="checkbox" aria-checked="false">選項</div>
<!-- ✅ 原生核取方塊 -->
<label><input type="checkbox"> 選項</label>
當需要 ARIA 時, 使用正確的角色和狀態。有關完整的頁籤清單範例,請參閱 ARIA 頁籤模式。
即時區域 (4.1.3)
使用 aria-live 區域來宣布動態內容變更,而無需移動焦點。有關標記和 showNotification() 輔助程式,請參閱即時區域模式。
測試檢查清單
自動化測試
優先使用能夠直接將失敗的渲染節點回傳給代理程式的即時 Lighthouse 稽核。使用 Chrome DevTools MCP 時,這是 lighthouse_audit。否則:
# Lighthouse 無障礙稽核
npx lighthouse https://example.com --only-categories=accessibility
# axe-core
npm install @axe-core/cli -g
axe https://example.com
手動測試
- 鍵盤導航: 使用 Tab 鍵瀏覽整個頁面,使用 Enter/Space 鍵觸發
- 螢幕閱讀器: 使用 VoiceOver (Mac)、NVDA (Windows) 或 TalkBack (Android) 進行測試
- 縮放: 在 200% 縮放下內容可用
- 高對比度: 使用 Windows 高對比度模式測試
- 減少動作: 使用
prefers-reduced-motion: reduce進行測試 - 焦點順序: 符合邏輯並遵循視覺順序
- 目標尺寸: 互動元素符合 24×24px 的最小要求
請參閱螢幕閱讀器指令參考,了解 VoiceOver 和 NVDA 的快捷鍵。
依影響程度分類的常見問題
嚴重(立即修復)
- 缺少表單標籤
- 缺少圖片替代文字
- 色彩對比不足
- 鍵盤陷阱
- 沒有焦點指示器
重要(上線前修復)
- 缺少頁面語言
- 缺少標題結構
- 無描述性的連結文字
- 自動播放媒體
- 缺少跳轉連結
中度(盡快修復)
- 圖示上缺少 ARIA 標籤
- 導航不一致
- 缺少錯誤識別
- 時間限制無控制項
- 缺少地標區域


