アクセシビリティ(a11y)
アクセシビリティ(a11y)は、障害の有無に関わらず全てのユーザーがUIを操作できるようにするための対応。キーボードだけでの操作、スクリーンリーダーでの読み上げ、色のコントラスト比など、Webアクセシビリティの基準(WCAG)に準拠するために必要。
主要UIライブラリはいずれもWAI-ARIAパターンに準拠したコンポーネントを提供しており、label と input の紐付けやフォーカストラップなどは自動で処理される。shadcn/ui は Radix UI をベースとしているため、特にアクセシビリティへの対応が厚い。
主なバリエーション
- •キーボードナビゲーション対応
- •ARIA属性の自動付与(aria-labelledby / aria-describedby)
- •フォーカス管理(フォーカストラップ・フォーカスリング)
- •スクリーンリーダー対応(VisuallyHidden / sr-only)
- •色のコントラスト比への配慮
- •WAI-ARIA パターン準拠(ダイアログ・ボタン・フォーム等)
ライブラリ横断比較
| 機能 | Mantine | Ant Design | shadcn/ui | MUI |
|---|---|---|---|---|
| ARIA属性の自動付与 | ○ 自動 | ○ 自動 | ○ Radix UI | ○ 自動 |
| キーボードナビゲーション | ○ 内蔵 | ○ 内蔵 | ○ Radix UI | ○ 内蔵 |
| フォーカス管理 | ○ FocusTrap内蔵 | ○ 内蔵 | ○ Radix FocusScope | ○ 内蔵 |
| スクリーンリーダー対応 | ○ VisuallyHidden | ○ aria-label | ○ sr-only class | ○ visuallyHidden |
| カラーコントラスト配慮 | ○ デフォルトAA | ○ デフォルトAA | ○ WCAG AA | ○ WCAG AA |
| WAI-ARIA パターン準拠 | ○ 主要パターン | ○ 主要パターン | ○ Radix UI準拠 | ○ 多数対応 |
○ = 対応 △ = 部分対応・制限あり × = 非対応
ライブラリ別コード例
各ライブラリでアクセシビリティ対応を実装する際の設定部分を抜粋しています。 動くデモは各比較ページでご確認ください。
Mantine
// Mantine のアクセシビリティ対応
import { Modal, Button, TextInput, ActionIcon, VisuallyHidden } from '@mantine/core';
import { useDisclosure } from '@mantine/hooks';
import { IconX } from '@tabler/icons-react';
// モーダルのフォーカストラップ(デフォルトで有効)
function AccessibleModal() {
const [opened, { open, close }] = useDisclosure(false);
return (
<>
<Button onClick={open}>ダイアログを開く</Button>
<Modal
opened={opened}
onClose={close}
title="確認"
trapFocus // デフォルト true:フォーカスをモーダル内に閉じ込める
closeOnEscape // デフォルト true:Esc で閉じる
>
<p>本当に削除しますか?</p>
<Button onClick={close} variant="subtle">キャンセル</Button>
</Modal>
</>
);
}
// スクリーンリーダー向けテキスト(VisuallyHidden)
<ActionIcon aria-label="閉じる">
<IconX size={16} />
<VisuallyHidden>パネルを閉じる</VisuallyHidden>
</ActionIcon>
// TextInput:label・error が aria-* に自動変換される
<TextInput
label="メールアドレス"
description="登録済みのアドレスを入力してください"
error="このアドレスは登録されていません"
// aria-describedby は Mantine が自動設定
/>Ant Design
// Ant Design のアクセシビリティ対応
import { Modal, Form, Input, Button } from 'antd';
import { DeleteOutlined } from '@ant-design/icons';
// Modal:キーボード操作・フォーカス管理(デフォルトで対応)
<Modal
open={open}
onCancel={() => setOpen(false)}
keyboard // Esc で閉じる(デフォルト: true)
focusTriggerAfterClose // 閉じた後にトリガー要素にフォーカスを戻す
title="確認ダイアログ"
aria-label="確認ダイアログ"
>
<p>本当に削除しますか?</p>
</Modal>
// Form.Item:htmlFor が自動で input に紐付けられる
<Form layout="vertical">
<Form.Item
label="メールアドレス"
name="email"
tooltip="登録済みのメールアドレスを入力してください"
rules={[{ required: true, message: '必須項目です' }]}
>
<Input aria-required="true" />
</Form.Item>
</Form>
// アイコンボタンの aria-label
<Button
icon={<DeleteOutlined />}
danger
aria-label="アイテムを削除"
/>
// Screen reader 向けのライブリージョン
import { message } from 'antd';
message.success('保存しました'); // aria-live="polite" で読み上げshadcn/ui
// shadcn/ui は Radix UI ベースで WAI-ARIA パターンを自動実装
import {
Dialog, DialogContent, DialogTitle, DialogDescription,
} from '@/components/ui/dialog';
import { Label } from '@/components/ui/label';
import { Input } from '@/components/ui/input';
import { Button } from '@/components/ui/button';
// Dialog:フォーカストラップ・Esc クローズは Radix UI が自動処理
// aria-labelledby・aria-describedby も自動設定
<Dialog open={open} onOpenChange={setOpen}>
<DialogContent>
<DialogTitle>確認</DialogTitle>
<DialogDescription>
本当に削除しますか?この操作は取り消せません。
</DialogDescription>
<div className="flex gap-2 justify-end">
<Button variant="outline" onClick={() => setOpen(false)}>キャンセル</Button>
<Button variant="destructive">削除</Button>
</div>
</DialogContent>
</Dialog>
// Label と Input の紐付け(htmlFor / id)
<div className="space-y-2">
<Label htmlFor="email">メールアドレス</Label>
<Input
id="email"
type="email"
aria-required="true"
aria-describedby="email-hint"
/>
<p id="email-hint" className="text-sm text-muted-foreground">
登録済みのアドレスを入力してください
</p>
</div>
// スクリーンリーダー向けテキスト(Tailwind sr-only)
<Button variant="ghost" size="icon" aria-label="閉じる">
<X className="h-4 w-4" aria-hidden="true" />
<span className="sr-only">パネルを閉じる</span>
</Button>MUI
// MUI のアクセシビリティ対応
import {
Dialog, DialogTitle, DialogContent, DialogContentText, DialogActions,
Button, TextField, IconButton,
} from '@mui/material';
import { visuallyHidden } from '@mui/utils';
import CloseIcon from '@mui/icons-material/Close';
// Dialog:aria-labelledby / aria-describedby で紐付け
<Dialog
open={open}
onClose={() => setOpen(false)}
aria-labelledby="dialog-title"
aria-describedby="dialog-description"
>
<DialogTitle id="dialog-title">
確認
<IconButton
aria-label="閉じる"
onClick={() => setOpen(false)}
sx={{ position: 'absolute', right: 8, top: 8 }}
>
<CloseIcon />
</IconButton>
</DialogTitle>
<DialogContent>
<DialogContentText id="dialog-description">
本当に削除しますか?この操作は取り消せません。
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={() => setOpen(false)}>キャンセル</Button>
<Button color="error" variant="contained">削除</Button>
</DialogActions>
</Dialog>
// TextField:label は input と自動紐付け(htmlFor 相当)
<TextField
label="メールアドレス"
error={!!error}
helperText={error || 'ヒントテキスト'} // aria-describedby 相当
inputProps={{ 'aria-required': true }}
/>
// スクリーンリーダー向けテキスト(visuallyHidden ユーティリティ)
<span style={visuallyHidden}>スクリーンリーダーのみ読み上げられるテキスト</span>まとめ・選び方のヒント
- •アクセシビリティを最優先にしたい → shadcn/ui(Radix UI ベースで WAI-ARIA パターンを厳密に実装)・MUI(長年にわたるアクセシビリティ改善の実績)
- •フォームのラベル紐付けを自動化したい → Mantine(
TextInputのlabelprop が自動的にhtmlForを設定)・MUI(TextFieldのlabelも同様) - •スクリーンリーダー向けに非表示テキストを追加したい → Mantine(
VisuallyHiddenコンポーネント)・MUI(visuallyHiddenユーティリティ)・shadcn/ui(Tailwind のsr-onlyクラス) - •ダイアログのフォーカストラップを確実に実装したい → 全ライブラリが内蔵しているが、shadcn/ui(Radix FocusScope)・Mantine(FocusTrap)が特に明示的にサポート
他の逆引きリファレンス