How to Streamline Accessibility with Claude Code
Learn how to streamline accessibility using Claude Code. Includes practical code examples and step-by-step guidance.
アクセシビリティ対応にClaude Codeを使うメリット
Webアクセシビリティの対応は重要ですが、WCAG基準の理解やARIA属性の正しい使い方は複雑です。Claude Codeはアクセシビリティのベストプラクティスを踏まえたコード生成と、既存コードの監査を効率的に行えます。
既存コードのアクセシビリティ監査
> src/components/ 配下の全コンポーネントをアクセシビリティ観点でレビューして。
> WCAG 2.1 AA基準でチェックして、問題と修正案を一覧にして。
Claude Codeは以下のような問題を検出します。
- 画像にalt属性が不足している
- フォーム要素にlabel要素が関連付けられていない
- カラーコントラスト比が不足している
- キーボード操作ができないインタラクティブ要素がある
- ARIA属性が不適切に使われている
アクセシブルなコンポーネントの生成
モーダルダイアログ
> アクセシブルなモーダルダイアログを作成して。
> フォーカストラップ、ESCキーでClose、背景のスクロールロックを実装して。
import { useEffect, useRef, useCallback } from 'react';
interface ModalProps {
isOpen: boolean;
onClose: () => void;
title: string;
children: React.ReactNode;
}
function Modal({ isOpen, onClose, title, children }: ModalProps) {
const modalRef = useRef<HTMLDivElement>(null);
const previousFocus = useRef<HTMLElement | null>(null);
// フォーカストラップ
const trapFocus = useCallback((e: KeyboardEvent) => {
if (!modalRef.current) return;
const focusable = modalRef.current.querySelectorAll<HTMLElement>(
'a[href], button:not([disabled]), input:not([disabled]), select, textarea, [tabindex]:not([tabindex="-1"])'
);
const first = focusable[0];
const last = focusable[focusable.length - 1];
if (e.key === 'Tab') {
if (e.shiftKey && document.activeElement === first) {
e.preventDefault();
last.focus();
} else if (!e.shiftKey && document.activeElement === last) {
e.preventDefault();
first.focus();
}
}
if (e.key === 'Escape') onClose();
}, [onClose]);
useEffect(() => {
if (isOpen) {
previousFocus.current = document.activeElement as HTMLElement;
document.body.style.overflow = 'hidden';
document.addEventListener('keydown', trapFocus);
// モーダル内の最初のフォーカス可能要素にフォーカス
setTimeout(() => {
modalRef.current?.querySelector<HTMLElement>('[autofocus], button')?.focus();
}, 0);
}
return () => {
document.body.style.overflow = '';
document.removeEventListener('keydown', trapFocus);
previousFocus.current?.focus();
};
}, [isOpen, trapFocus]);
if (!isOpen) return null;
return (
<div
className="fixed inset-0 z-50 flex items-center justify-center"
role="presentation"
>
<div
className="fixed inset-0 bg-black/50"
aria-hidden="true"
onClick={onClose}
/>
<div
ref={modalRef}
role="dialog"
aria-modal="true"
aria-labelledby="modal-title"
className="relative z-10 mx-4 w-full max-w-lg rounded-lg bg-white p-6 shadow-xl dark:bg-gray-800"
>
<h2 id="modal-title" className="text-xl font-bold mb-4">
{title}
</h2>
{children}
<button
onClick={onClose}
aria-label="Close"
className="absolute right-4 top-4 rounded-full p-1 hover:bg-gray-100 dark:hover:bg-gray-700"
>
<svg aria-hidden="true" className="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" />
</svg>
</button>
</div>
</div>
);
}
アクセシブルなドロップダウンメニュー
function DropdownMenu({ label, items }: { label: string; items: MenuItem[] }) {
const [isOpen, setIsOpen] = useState(false);
const [activeIndex, setActiveIndex] = useState(-1);
const menuRef = useRef<HTMLUListElement>(null);
const handleKeyDown = (e: React.KeyboardEvent) => {
switch (e.key) {
case 'ArrowDown':
e.preventDefault();
setActiveIndex(prev => Math.min(prev + 1, items.length - 1));
break;
case 'ArrowUp':
e.preventDefault();
setActiveIndex(prev => Math.max(prev - 1, 0));
break;
case 'Enter':
case ' ':
e.preventDefault();
if (activeIndex >= 0) items[activeIndex].onClick();
setIsOpen(false);
break;
case 'Escape':
setIsOpen(false);
break;
}
};
return (
<div className="relative" onKeyDown={handleKeyDown}>
<button
aria-haspopup="true"
aria-expanded={isOpen}
onClick={() => setIsOpen(!isOpen)}
>
{label}
</button>
{isOpen && (
<ul ref={menuRef} role="menu" className="absolute mt-1 rounded-md border bg-white shadow-lg dark:bg-gray-800">
{items.map((item, i) => (
<li
key={i}
role="menuitem"
tabIndex={-1}
className={`cursor-pointer px-4 py-2 ${i === activeIndex ? 'bg-blue-100 dark:bg-blue-900' : ''}`}
onClick={item.onClick}
>
{item.label}
</li>
))}
</ul>
)}
</div>
);
}
フォームのアクセシビリティ
> お問い合わせフォームをアクセシブルに作り直して。
> バリデーションエラーもスクリーンリーダーに伝わるようにして。
function ContactForm() {
const [errors, setErrors] = useState<Record<string, string>>({});
return (
<form aria-label="お問い合わせフォーム" noValidate>
<div className="mb-4">
<label htmlFor="name" className="block font-medium mb-1">
Name <span aria-label="required">*</span>
</label>
<input
id="name"
type="text"
required
aria-required="true"
aria-invalid={!!errors.name}
aria-describedby={errors.name ? 'name-error' : undefined}
className="w-full rounded border p-2"
/>
{errors.name && (
<p id="name-error" role="alert" className="mt-1 text-sm text-red-600">
{errors.name}
</p>
)}
</div>
<div className="mb-4">
<label htmlFor="email" className="block font-medium mb-1">
メールアドレス <span aria-label="required">*</span>
</label>
<input
id="email"
type="email"
required
aria-required="true"
aria-invalid={!!errors.email}
aria-describedby="email-hint email-error"
className="w-full rounded border p-2"
/>
<p id="email-hint" className="mt-1 text-xs text-gray-500">
e.g., [email protected]
</p>
{errors.email && (
<p id="email-error" role="alert" className="mt-1 text-sm text-red-600">
{errors.email}
</p>
)}
</div>
<button type="submit" className="rounded bg-blue-600 px-4 py-2 text-white">
送信
</button>
</form>
);
}
自動テストの導入
> jest-axeを使ったアクセシビリティテストを追加して。
import { render } from '@testing-library/react';
import { axe, toHaveNoViolations } from 'jest-axe';
expect.extend(toHaveNoViolations);
describe('Modal accessibility', () => {
it('should have no accessibility violations', async () => {
const { container } = render(
<Modal isOpen={true} onClose={() => {}} title="Test Modal">
<p>Content</p>
</Modal>
);
const results = await axe(container);
expect(results).toHaveNoViolations();
});
});
Summary
Claude Codeを使えば、WCAG基準に沿ったアクセシブルなコンポーネントの生成から既存コードの監査まで効率的に行えます。フック機能でアクセシビリティテストを自動実行する設定も有効です。詳しくはフック機能ガイドを参照してください。日々の開発フローに組み込むコツは生産性を3倍にするTipsで紹介しています。
Claude Codeの詳細はAnthropic公式ドキュメントをご覧ください。WCAGガイドラインの詳細はW3C WCAG 2.1を参照してください。
Related Posts
10 Tips to Triple Your Productivity with Claude Code
Learn about 10 tips to triple your productivity using Claude Code. Practical tips and code examples included.
Canvas/WebGL Optimization with Claude Code
Learn about canvas/webgl optimization using Claude Code. Practical tips and code examples included.
Markdown Implementation with Claude Code
Learn about markdown implementation using Claude Code. Practical tips and code examples included.