無障礙審計技能
名稱:a11y-audit 層級:STANDARD 類別:工程 - 前端品質 依賴:Python 3.8+(僅標準函式庫) 作者:Alireza Rezvani 版本:2.1.2 最後更新:2026-03-18 授權:MIT
名稱
a11y-audit -- WCAG 2.2 無障礙審計與修復技能
說明
a11y-audit 技能為現代 Web 應用程式提供了一套完整的無障礙審計管線。它實現了一個三階段的工作流程 —— 掃描、修復、驗證 —— 能夠識別 WCAG 2.2 Level A 與 AA 的違規項目,針對特定框架生成精確的修復程式碼,並產出適合利害關係人閱讀的合規報告。
此技能不僅止於偵測。對於發現的每個違規項目,它都能提供針對您所使用框架(React、Next.js、Vue、Angular、Svelte 或純 HTML)量身定做的精確前/後程式碼修復範例。它理解 React JSX 中 <img> 元素缺少 alt 屬性所需的修復模式,與 Vue SFC 或 Angular 模板中的相同問題截然不同。
此技能的功能:
- 掃描您的程式碼庫,找出所有 WCAG 2.2 Level A 與 AA 的違規項目,並按嚴重性分類(重大、主要、次要)
- 修復每個違規項目,提供特定框架的前/後程式碼模式
- 驗證修復結果是否解決了原始違規問題,且未引入任何回歸
- 報告以結構化格式呈現調查結果,適用於開發者、專案經理及合規利害關係人
- 整合至 CI/CD 管線,以防止無障礙問題回歸
主要差異化特點:
- 框架感知的修復模式(非泛泛的 HTML 建議)
- 色彩對比分析,並提供可存取的替代色彩建議
- WCAG 2.2 涵蓋範圍,包括最新的成功標準(焦點外觀、拖曳移動、目標尺寸)
- 支援 GitHub Actions、GitLab CI 及 Azure DevOps 的 CI/CD 管線整合
- 透過
/a11y-audit斜線指令支援
功能
核心能力
| 功能 | 說明 |
|---|---|
| 完整 WCAG 2.2 掃描 | 檢查程式碼庫中所有 Level A 與 AA 的成功標準 |
| 框架偵測 | 自動偵測 React、Next.js、Vue、Angular、Svelte 或純 HTML |
| 嚴重性分類 | 將每個違規項目分類為重大、主要或次要 |
| 修復程式碼生成 | 為每個問題提供前/後程式碼差異對比 |
| 色彩對比檢查器 | 驗證前景/背景組合是否符合 AA 與 AAA 比率 |
| 可存取的替代方案 | 建議符合對比要求的替代色彩 |
| 合規報告 | 生成包含通過/未通過摘要的利害關係人報告 |
| CI/CD 整合 | GitHub Actions、GitLab CI、Azure DevOps 管線設定 |
| 鍵盤導航審計 | 偵測缺少的焦點管理與頁籤順序問題 |
| ARIA 驗證 | 檢查不正確、冗餘或缺失的 ARIA 屬性 |
| 即時區域偵測 | 識別缺少 aria-live 宣告的動態內容 |
| 表單無障礙性 | 驗證標籤關聯、錯誤訊息與輸入類型 |
WCAG 2.2 涵蓋範圍矩陣
| 原則 | Level A 標準 | Level AA 標準 |
|---|---|---|
| 可感知 | 1.1.1 非文字內容, 1.2.1-1.2.3 以時間為基礎的媒體, 1.3.1-1.3.3 可適應, 1.4.1-1.4.2 可區辨 | 1.3.4-1.3.5 可適應, 1.4.3-1.4.5 對比與文字圖像, 1.4.10-1.4.13 重新流動與內容 |
| 可操作 | 2.1.1-2.1.2 鍵盤, 2.2.1-2.2.2 時間, 2.3.1 癲癇, 2.4.1-2.4.4 可導航, 2.5.1-2.5.4 輸入 | 2.4.5-2.4.7 可導航, 2.4.11 焦點外觀 (NEW 2.2), 2.5.7 拖曳 (NEW 2.2), 2.5.8 目標尺寸 (NEW 2.2) |
| 可理解 | 3.1.1 語言, 3.2.1-3.2.2 可預測, 3.3.1-3.3.2 輸入輔助 | 3.1.2 部分語言, 3.2.3-3.2.4 可預測, 3.3.3-3.3.4 錯誤處理, 3.3.7 冗餘輸入 (NEW 2.2), 3.3.8 可存取的驗證 (NEW 2.2) |
| 穩健 | 4.1.2 名稱/角色/值 | 4.1.3 狀態訊息 |
嚴重性定義
| 嚴重性 | 定義 | 範例 | SLA |
|---|---|---|---|
| 重大 | 阻礙整個使用者群體存取 | 資訊型圖片缺少替代文字,主要導航無法使用鍵盤操作 | 在發行前修復 |
| 主要 | 顯著障礙,降低使用體驗 | 內文色彩對比不足,缺少表單標籤 | 在當前衝刺內修復 |
| 次要 | 可用性問題,造成摩擦 | 冗餘的 ARIA 角色,標題層級結構不佳 | 在接下來 2 個衝刺內修復 |
使用方式
快速開始
啟用該技能並在您的專案上執行審計:
# 掃描整個專案
python scripts/a11y_scanner.py /path/to/project
# 掃描並輸出 JSON 格式給工具使用
python scripts/a11y_scanner.py /path/to/project --json
# 檢查特定值的色彩對比
python scripts/contrast_checker.py --fg "#777777" --bg "#ffffff"
# 檢查 CSS/Tailwind 檔案中的對比
python scripts/contrast_checker.py --file /path/to/styles.css
斜線指令
使用 /a11y-audit 斜線指令進行互動式審計會話:
/a11y-audit # 審計當前專案
/a11y-audit --scope src/ # 審計特定目錄
/a11y-audit --fix # 審計並自動套用修復
/a11y-audit --report # 生成利害關係人報告
/a11y-audit --ci # 輸出 CI 相容的結果
三階段工作流程
階段 1:掃描
掃描器會遍歷您的原始碼樹,偵測所使用的框架,並套用適當的規則集。
python scripts/a11y_scanner.py /path/to/project --format table
範例輸出:
A11Y 審計報告 - /path/to/project
偵測到框架:React (Next.js)
掃描檔案數:127
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
重大問題(3 個)
[1.1.1] src/components/Hero.tsx:14
在 <img> 元素上缺少替代文字
[2.1.1] src/components/Modal.tsx:8
焦點未鎖定在模態對話框內
[1.4.3] src/styles/globals.css:42
在 .subtitle 上對比度 2.8:1(需要 4.5:1)
主要問題(7 個)
[2.4.11] src/components/Button.tsx:22
焦點指示器不可見(需要 2px 外框)
[1.3.1] src/components/Form.tsx:31
輸入框缺少關聯的 <label>
...
次要問題(4 個)
[1.3.1] src/components/Nav.tsx:5
<nav> 具有冗餘的 role="navigation"
...
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
摘要:14 項違規(3 項重大,7 項主要,4 項次要)
WCAG 2.2 Level A: 8 個問題
WCAG 2.2 Level AA: 6 個問題
階段 2:修復
對於每個違規項目,套用特定框架的修復。此技能為每種問題類型提供精確的前/後程式碼。
請參閱下方的按框架分類的修復模式章節,以取得完整的修復目錄。
階段 3:驗證
重新執行掃描器,確認所有修復已套用且未引入任何回歸:
# 修復後重新掃描
python scripts/a11y_scanner.py /path/to/project --format table
# 與基準線比較
python scripts/a11y_scanner.py /path/to/project --baseline audit-baseline.json
驗證輸出:
驗證結果
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
前次掃描: 14 項違規(3 項重大,7 項主要,4 項次要)
當前掃描: 2 項違規(0 項重大,1 項主要,1 項次要)
已解決: 12 項違規
新增問題: 0 項回歸
狀態:已改善(減少 85.7%)
範例
範例 1:React 元件審計
給定一個具有多個無障礙問題的 React 元件:
// BEFORE: src/components/ProductCard.tsx
function ProductCard({ product }) {
return (
<div onClick={() => navigate(`/product/${product.id}`)}>
<img src={product.image} />
<div style={{ color: '#aaa', fontSize: '12px' }}>
{product.name}
</div>
<span style={{ color: '#999' }}>${product.price}</span>
</div>
);
}
偵測到的違規項目:
| # | WCAG | 嚴重性 | 問題 |
|---|---|---|---|
| 1 | 1.1.1 | 重大 | <img> 缺少 alt 屬性 |
| 2 | 2.1.1 | 重大 | <div onClick> 無法使用鍵盤操作 |
| 3 | 1.4.3 | 主要 | 顏色 #aaa 在白色背景上對比失敗(2.32:1,需要 4.5:1) |
| 4 | 1.4.3 | 主要 | 顏色 #999 在白色背景上對比失敗(2.85:1,需要 4.5:1) |
| 5 | 4.1.2 | 主要 | 互動元素缺少角色和可存取名稱 |
// AFTER: src/components/ProductCard.tsx
function ProductCard({ product }) {
return (
<a
href={`/product/${product.id}`}
className="product-card"
aria-label={`查看 ${product.name} - $${product.price}`}
>
<img src={product.image} alt={product.imageAlt || product.name} />
<div style={{ color: '#595959', fontSize: '12px' }}>
{product.name}
</div>
<span style={{ color: '#767676' }}>${product.price}</span>
</a>
);
}
變更內容:
- 將
<div onClick>替換為<a href>以獲得原生的鍵盤和螢幕閱讀器支援 - 為
<img>添加alt屬性,並提供有意義的備用文字 - 使用
aria-label為輔助技術提供完整上下文 - 顏色
#aaa替換為#595959(對比度 7.01:1 -- 通過 AA 和 AAA) - 顏色
#999替換為#767676(對比度 4.54:1 -- 通過 AA)
範例 2:Vue SFC 表單審計
<!-- BEFORE: src/components/LoginForm.vue -->
<template>
<form @submit="handleLogin">
<input type="text" placeholder="電子郵件" v-model="email" />
<input type="password" placeholder="密碼" v-model="password" />
<div v-if="error" style="color: red">{{ error }}</div>
<div @click="handleLogin">登入</div>
</form>
</template>
偵測到的違規項目:
| # | WCAG | 嚴重性 | 問題 |
|---|---|---|---|
| 1 | 1.3.1 | 重大 | 輸入框缺少關聯的 <label> 元素 |
| 2 | 3.3.2 | 主要 | 僅使用預留位置文字作為標籤(輸入時會消失) |
| 3 | 2.1.1 | 重大 | <div @click> 無法使用鍵盤操作 |
| 4 | 4.1.3 | 主要 | 錯誤訊息未向螢幕閱讀器宣告 |
| 5 | 3.3.1 | 主要 | 錯誤未以程式方式與輸入關聯 |
<!-- AFTER: src/components/LoginForm.vue -->
<template>
<form @submit.prevent="handleLogin" aria-label="登入您的帳戶">
<div class="field">
<label for="login-email">電子郵件</label>
<input
id="login-email"
type="email"
v-model="email"
autocomplete="email"
required
:aria-describedby="emailError ? 'email-error' : undefined"
:aria-invalid="!!emailError"
/>
<span v-if="emailError" id="email-error" role="alert">
{{ emailError }}
</span>
</div>
<div class="field">
<label for="login-password">密碼</label>
<input
id="login-password"
type="password"
v-model="password"
autocomplete="current-password"
required
:aria-describedby="passwordError ? 'password-error' : undefined"
:aria-invalid="!!passwordError"
/>
<span v-if="passwordError" id="password-error" role="alert">
{{ passwordError }}
</span>
</div>
<div v-if="error" role="alert" aria-live="assertive" class="form-error">
{{ error }}
</div>
<button type="submit">登入</button>
</form>
</template>
範例 3:Angular 模板審計
<!-- BEFORE: src/app/dashboard/dashboard.component.html -->
<div class="tabs">
<div *ngFor="let tab of tabs"
(click)="selectTab(tab)"
[class.active]="tab.active">
{{ tab.label }}
</div>
</div>
<div class="tab-content">
<div *ngIf="selectedTab">{{ selectedTab.content }}</div>
</div>
偵測到的違規項目:
| # | WCAG | 嚴重性 | 問題 |
|---|---|---|---|
| 1 | 4.1.2 | 重大 | 頁籤 widget 缺少 ARIA 角色(tablist、tab、tabpanel) |
| 2 | 2.1.1 | 重大 | 頁籤無法使用鍵盤導航(方向鍵、Home、End) |
| 3 | 2.4.11 | 主要 | 活動頁籤上沒有可見的焦點指示器 |
<!-- AFTER: src/app/dashboard/dashboard.component.html -->
<div class="tabs" role="tablist" aria-label="儀表板區段">
<button
*ngFor="let tab of tabs; let i = index"
role="tab"
[id]="'tab-' + tab.id"
[attr.aria-selected]="tab.active"
[attr.aria-controls]="'panel-' + tab.id"
[attr.tabindex]="tab.active ? 0 : -1"
(click)="selectTab(tab)"
(keydown)="handleTabKeydown($event, i)"
class="tab-button"
[class.active]="tab.active">
{{ tab.label }}
</button>
</div>
<div
*ngIf="selectedTab"
role="tabpanel"
[id]="'panel-' + selectedTab.id"
[attr.aria-labelledby]="'tab-' + selectedTab.id"
tabindex="0"
class="tab-content">
{{ selectedTab.content }}
</div>
用於鍵盤導覽的支援 TypeScript:
// dashboard.component.ts
handleTabKeydown(event: KeyboardEvent, index: number): void {
const tabCount = this.tabs.length;
let newIndex = index;
switch (event.key) {
case 'ArrowRight':
newIndex = (index + 1) % tabCount;
break;
case 'ArrowLeft':
newIndex = (index - 1 + tabCount) % tabCount;
break;
case 'Home':
newIndex = 0;
break;
case 'End':
newIndex = tabCount - 1;
break;
default:
return;
}
event.preventDefault();
this.selectTab(this.tabs[newIndex]);
// 將焦點移至新的頁籤按鈕
const tabElement = document.getElementById(`tab-${this.tabs[newIndex].id}`);
tabElement?.focus();
}
範例 4:Next.js 頁面層級審計
// BEFORE: src/app/page.tsx
export default function Home() {
return (
<main>
<div className="text-4xl font-bold">歡迎來到 Acme</div>
<div className="mt-4">
使用我們的平台打造更好的產品。
</div>
<div className="mt-8 bg-blue-600 text-white px-6 py-3 rounded cursor-pointer"
onClick={() => router.push('/signup')}>
開始使用
</div>
</main>
);
}
偵測到的違規項目:
| # | WCAG | 嚴重性 | 問題 |
|---|---|---|---|
| 1 | 2.1.1 | 重大 | 按鈕不具備鍵盤可操作性,且缺少角色 |
| 2 | 4.1.2 | 主要 | 缺少 role="button" 或使用原生 <button> 元素 |
| 3 | 1.4.1 | 主要 | 藍色按鈕背景上的白色文字可能缺乏足夠的對比。 |