在前端開發中,無障礙(a11y)已成為不可忽視的議題。這篇文章會帶你快速了解 React、Vue 與 Angular 目前最受歡迎的可存取性工具庫,並示範如何將它們落地到實際專案。
透過實例與範疇說明,你能立刻把握各框架在無障礙設計上的優勢、限制,以及最佳整合方式,讓網站或應用程式更友善於所有使用者。
主流無障礙庫概覽
在 React、Vue 與 Angular 的生態系中,以下是目前最常被採用且維護良好的可存取性工具:
- React:
react-aria, @react-aria/*, 以及曾經流行的 reach-ui(已轉移至 @react-aria)
- Vue:
vue-a11y, @vueuse/core 的 focus-trap、a11y-dialog-vue 等輔助套件
- Angular:Angular CDK Accessibility (
@angular/cdk/a11y) 以及官方的 ARIA 指南套件
React - react-aria 範例
react-aria 提供零依賴、可組合化的 Hook,讓你能快速為元件加入 ARIA 屬性。以下示範一個簡單的按鈕:
import { useButton } from 'react-aria';
export function AccessibleBtn(props) {
const ref = React.useRef();
const { buttonProps } = useButton({ onPress: props.onClick }, ref);
return <button {...buttonProps} ref={ref}>{props.label}</button>;
}
Vue - vue-a11y 與 focus-trap 範例
Vue 的 vue-a11y 主要提供簡單的指令與組件,結合 @vueuse/core 的 focus‑trap 可實作可存取性對話框:
<template>
<button @click="open = true">開啟對話框</button>
<dialog v-if="open" ref="dlg"
:class="{'hidden': !open}"
@keydown.esc="closeDialog"
tabindex="-1">
<p>這是一個可存取性對話框。</p>
<button @click="closeDialog">關閉</button>
</dialog>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue';
import { useFocusTrap } from '@vueuse/core';
const open = ref(false);
const dlg = ref<HTMLDialogElement | null>(null);
function closeDialog() { open.value = false; }
// 啟用 focus-trap 時,將焦點鎖定在對話框內
watch(open, (newVal) => { if(newVal && dlg.value){ useFocusTrap(dlg.value); } });
</script>
Angular - CDK Accessibility 範例
Angular CDK 提供 AriaLiveRegion, FocusMonitor 等工具,可輕鬆實作動態訊息與焦點管理:
import { Component } from '@angular/core';
import { AriaLiveRegion, LiveMessageService } from '@angular/cdk/a11y';
@Component({ selector: 'app-alert', template: '<button (click)"notify()">送出訊息</button>' })
export class AlertComponent {
constructor(private liveRegion: AriaLiveRegion, private liveService: LiveMessageService) {}
notify(){ this.liveService.message('已成功傳送!', 'assertive'); }
}
測試與自動化:Axe Core 與 Cypress
為確保無障礙功能正常,建議結合 Axe Core 進行靜態分析,並在 Cypress 或 Playwright 中加入 axe 擴充套件。以下示範在 Cypress 裡使用:
import { addMatchImageSnapshotCommand } from 'cypress-image-snapshot';
// 將 axe 添加至 Cypress 命令
Cypress.Commands.add('checkA11y', () => {
cy.injectAxe();
cy.checkA11y();
});
// 在測試中呼叫
describe('可存取性檢查', () => {
it('應該通過 axe 分析', () => {
cy.visit('/');
cy.checkA11y();
});
});