アクセシビリティ(a11y)

アクセシビリティ(a11y)は、障害の有無に関わらず全てのユーザーがUIを操作できるようにするための対応。キーボードだけでの操作、スクリーンリーダーでの読み上げ、色のコントラスト比など、Webアクセシビリティの基準(WCAG)に準拠するために必要。

主要UIライブラリはいずれもWAI-ARIAパターンに準拠したコンポーネントを提供しており、label と input の紐付けやフォーカストラップなどは自動で処理される。shadcn/ui は Radix UI をベースとしているため、特にアクセシビリティへの対応が厚い。

主なバリエーション
  • キーボードナビゲーション対応
  • ARIA属性の自動付与(aria-labelledby / aria-describedby)
  • フォーカス管理(フォーカストラップ・フォーカスリング)
  • スクリーンリーダー対応(VisuallyHidden / sr-only)
  • 色のコントラスト比への配慮
  • WAI-ARIA パターン準拠(ダイアログ・ボタン・フォーム等)

ライブラリ横断比較

機能MantineAnt Designshadcn/uiMUI
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(TextInputlabel prop が自動的に htmlFor を設定)・MUI(TextFieldlabel も同様)
  • スクリーンリーダー向けに非表示テキストを追加したい → Mantine(VisuallyHidden コンポーネント)・MUI(visuallyHidden ユーティリティ)・shadcn/ui(Tailwind の sr-only クラス)
  • ダイアログのフォーカストラップを確実に実装したい → 全ライブラリが内蔵しているが、shadcn/ui(Radix FocusScope)・Mantine(FocusTrap)が特に明示的にサポート