こんにちは、弁護士ドットコムでクラウドサインというサービスのフロントエンドを実装している林 @rinyu_tech です。
最近は、AI コーディングで UI コンポーネントを生成させることも増えてきました。しかし、AI はアクセシビリティに配慮しないコードを生成することもあります。今回は、AI にアクセシブルなコードを生成させることが可能なのか、実験してみました。
実験概要
目的
アクセシブルな Web アプリケーションを作るときの「シフトレフト」戦略として、AI の実装後にアクセシビリティのレビューと修正するのではなく、最初からアクセシブルなコードを生成させることが狙いです。AI コーディングを ESLint アクセシビリティプラグインと組み合わせて、その効果を確かめました。
具体的には Vue.js プロジェクトで、Claude Code を使った AI コーディングを行いました。AI に ESLint の eslint-plugin-vuejs-accessibility を利用させることで、どれくらいアクセシビリティの品質が向上するのかを実証しました。
検証対象
ドロップダウンメニューを題材に、AI コーディングの 3 つのやり方を比べてみました。
- 普通にAIに指示する: アクセシビリティに関する指示はプロンプトに含めない
- アクセシビリティを指示する:「アクセシビリティを考慮してください」をプロンプトに含める
- リンターフィードバックを活用: ESLint アクセシビリティプラグインからのフィードバックをコード生成時から意識させる
実験結果のまとめ
3 つのアプローチでドロップダウンメニューを実装し、ESLint アクセシビリティプラグインで検証した結果を以下にまとめます。
| やり方 | ESLint警告数 | 主な問題 |
|---|---|---|
| (1) 普通にAIに指示する | 4個 | セマンティック構造、キーボード対応なし |
| (2) アクセシビリティを指示する | 1個 | 不適切なrole使用、インタラクティブ要素構造の問題 |
| (3) リンターフィードバックを活用 | 0個 | ESLint警告なし(リンター検出外の細かな問題あり) |

上の図は 3 つのアプローチで実装したドロップダウンメニューの比較結果です。左から順に(1)通常の AI コーディング、(2)アクセシビリティ配慮指示、(3)リンター活用版となっており、それぞれの実装の特徴と改善点が確認できます。特に(3)リンター活用版では、ESLint 警告が 0 個となり、比較的高いアクセシビリティ準拠を実現していることがわかります。ただし、リンターでは検出されない細かな記述の問題は残っています。
実験環境
プロジェクトの構成
- フレームワーク: Vue 3 + TypeScript
- ビルドツール: Vite
- リンター: ESLint + eslint-plugin-vuejs-accessibility v2.4.1
設定したアクセシビリティのルール
ドロップダウンメニュー開発に必要なアクセシビリティルールを包括的に設定しました。
インタラクティブ要素(ドロップダウンに必須)
click-events-have-key-events: クリックイベントにキーボード対応を要求mouse-events-have-key-events: マウスイベントにキーボード対応を要求no-static-element-interactions: 非インタラクティブ要素の誤用を防止interactive-supports-focus: インタラクティブ要素のフォーカス対応チェック
ARIA・セマンティックHTML(スクリーンリーダー対応)
aria-props: ARIA 属性の正しい使用チェックaria-role: 有効な ARIA ロールの検証aria-unsupported-elements: サポートされない要素での ARIA 使用を防止role-has-required-aria-props: ARIA ロールの必須属性チェックno-redundant-roles: 冗長な ARIA ロールの使用を防止
フォーカス管理(キーボードナビゲーション)
no-autofocus: 自動フォーカスの制限tabindex-no-positive: 正の tabindex 値を禁止no-aria-hidden-on-focusable: フォーカス可能要素での aria-hidden 使用を防止
実験過程
Phase 1: 通常のAIコーディング実装
プロンプト:「Vue.js でドロップダウンメニューを作ってください。選択肢から項目を選ぶことができる機能を実装してください」
<!-- Phase 1 で出力された実装 -->
<div class="dropdown-trigger" @click="toggleDropdown">
{{ selectedItem }}
<span class="arrow">▼</span>
</div>
<div v-if="isOpen" class="dropdown-menu">
<div
v-for="item in items"
class="dropdown-item"
@click="selectItem(item)"
>
{{ item }}
</div>
</div>
この実装は、見た目と基本的な機能は正常に動作しますが、アクセシビリティの観点では多くの問題を抱えています。
ドロップダウンメニューには、標準の HTML 要素が存在しません。そのため、適切なセマンティックな構造と ARIA 属性なしでは、スクリーンリーダーがドロップダウンメニューとして認識できません。この問題については、以前に「アクセシブルなドロップダウンメニューの構成」という記事で、手動実装による解決方法を詳しく解説しました。よければご覧ください。
この実装では、すべての要素が <div> で構成され、必要な ARIA 情報も不足しています。
またキーボード操作にも対応していません。マウスでのクリック操作のみを前提とした設計になっているため、キーボードのみでの操作やタブキーによるナビゲーションができません。これは身体的制約のあるユーザーにとって大きな障壁となります。
さらに、HTML 標準にないカスタムコンポーネントであるにもかかわらず、要素の名前や状態を補足できていないため、支援技術がメニューの開閉状態や各項目の意味を適切に伝えることができません。
ESLintで検出された具体的な問題(4つの警告):
click-events-have-key-events: クリックハンドラーにキーボードリスナーなしno-static-element-interactions: 非インタラクティブ要素(div)にクリックハンドラーrole-has-required-aria-props: 必要な ARIA 属性の不足interactive-supports-focus: インタラクティブ要素のフォーカス対応不足
Phase 2: アクセシビリティ配慮指示
プロンプト:「Vue.js でアクセシビリティを考慮したドロップダウンメニューを実装してください」
<!-- Phase 2 で出力された実装 -->
<button
class="dropdown-trigger"
type="button"
:aria-expanded="isOpen"
aria-haspopup="listbox" <!-- ❌ 不適切なhaspopup値 -->
@click="toggleDropdown"
@keydown="handleKeyDown"
>
{{ selectedItem }}
</button>
<div v-show="isOpen" class="dropdown-menu" role="listbox">
<div
v-for="(item, index) in items"
class="dropdown-item" <!-- ❌ クリック可能なdiv要素 -->
role="option" <!-- ❌ 不適切なrole使用 -->
:tabindex="selectedIndex === index ? 0 : -1"
:aria-selected="selectedItem === item"
@click="selectItem(item)"
@keydown="handleItemKeyDown($event)"
>
{{ item }}
</div>
</div>
明示的な配慮指示により、Phase 1 と比べて大幅な改善が見られました。トリガー部分では <button> 要素を使用し、aria-expanded による開閉状態の通知を実装しました。また基本的なキーボードナビゲーション(矢印キー、Enter、Escape)、外部クリック対応など、多くのアクセシビリティ機能が実装されました。
しかし、設計パターンの選択に問題が残りました。ドロップダウンメニューに listbox/option パターンを使用していますが、これは本来セレクトボックス用の設計パターンです。ドロップダウンメニューには menu/menuitem パターンが適切であり、またクリック可能な要素に <div> を使用している点も問題です。
まだ残っている問題(1つの警告):
Elements with the "option" interactive role must be focusable: role="option"の不適切な使用
Phase 3: リンター活用による完全準拠
プロンプト:「Vue.js でドロップダウンメニューを実装してください。アクセシビリティリンターの警告に従って段階的に修正しながら進めてください」
<!-- Phase 3 で出力された実装 -->
<button
ref="triggerRef"
type="button"
class="dropdown-trigger"
:aria-expanded="isOpen"
aria-haspopup="menu" <!-- ✅ 正しいhaspopup値 -->
:aria-labelledby="'dropdown-button'" <!-- ⚠️ ボタン内にテキストがあるため不要 -->
@click="toggleDropdown"
@keydown="handleKeydown"
>
<span id="dropdown-button">{{ selectedItem }}</span>
<span class="arrow" aria-hidden="true">▼</span>
</button>
<ul <!-- ✅ セマンティックなリスト構造 -->
v-if="isOpen"
ref="menuRef"
class="dropdown-menu"
role="menu" <!-- ✅ 正しいメニューrole -->
:aria-labelledby="'dropdown-button'" <!-- ⚠️ 引用符が不要、aria-labelledby="dropdown-button"が正しい -->
>
<li v-for="(item, index) in items" :key="item" role="none">
<button <!-- ✅ 適切なインタラクティブ要素 -->
type="button"
class="dropdown-item"
role="menuitem" <!-- ✅ 正しいメニュー項目role -->
:tabindex="focusedIndex === index ? 0 : -1"
@click="selectItem(item)"
@keydown="handleMenuItemKeydown($event)"
>
{{ item }}
</button>
</li>
</ul>
ESLint からの具体的な指摘をもとに段階的に改善していくと、これまでのフェーズで残っていた問題がすべて解決されました。
特に印象的だったのは、設計パターンの根本的な見直しができたことです。Phase 2 では listbox/option という、本来セレクトボックス用のパターンを使っていました。しかし、リンターの警告により menu/menuitem という正しいドロップダウンメニュー用のパターンに修正できました。またインタラクティブ要素が <div> と <button> で混在していた問題も、一貫して <button> に統一することで解決しました。
ただし、実験でカバーできなかった部分として、aria-labelledby の記述方法があります。ボタンでは内部テキストがあるため aria-labelledby は不要ですが、メニューでは必要です。また Vue.js の属性バインディングで 'dropdown-button' と余分な引用符が付いてしまいました。正しくは aria-labelledby="dropdown-button" とすべきでした。この種の細かな最適化は、ESLint では検出されないため手動でのレビューが必要になります。
結果: ESLint の警告を完全に解消(0 個)
機能比較
| 機能 | (1)指示なし | (2)配慮指示 | (3)リンター活用 |
|---|---|---|---|
| 基本動作 | ✅ | ✅ | ✅ |
| キーボード操作 | ❌ | ✅ | ✅ |
| ARIA属性 | ❌ | ✅部分的 | ✅完全 |
| フォーカス管理 | ❌ | ✅基本 | ✅完全 |
| 外部クリック | ❌ | ✅ | ✅ |
| セマンティックHTML | ❌ | ⚠️ | ✅ |
この結果を見ると、明示的なアクセシビリティ指示の効果は非常に大きいことがわかります。基本動作以外のすべての項目で改善が見られ、特にキーボード操作や外部クリック対応は完全に実装されました。
評価の基準について説明します。「基本」は最低限の機能は動く状態「部分的」は一部に不適切な実装がある状態を表しています。「完全」はベストプラクティスに従った適切な実装「⚠️(警告)」は動作するが設計上の問題がある状態を意味します。
興味深いのが、ARIA 属性とフォーカス管理の項目です。配慮指示では「部分的」「基本」となっていますが、機能自体は動くものの、設計パターンが適切でないことを表しています。一方リンター活用では「完全」となり、根本的な設計の見直しまで実現できました。
もっとも印象的だったのは、セマンティック HTML の項目です。配慮指示では ⚠️(警告)となっているように、表面的には改善されているように見えるが根本的な問題が残っている状態でした。リンターの具体的な指摘により、この隠れた問題も完全に解決できました。
重要な発見
AIコーディングでよくある問題
今回の実験でわかったのは、AI にアクセシビリティ対応をお願いしても、しばしば表面的な改善にとどまってしまうということです。
特に ARIA 属性については、知識が断片的になりがちです。「アクセシビリティに配慮してください」と指示すると、確かに ARIA 属性は使ってくれるのですが、その意味と実装の一致しないことがよくあります。今回も role="option" を設定したのにクリック操作で実装してしまうという問題が起きました。
また設計パターンの選択でも混乱が見られました。ドロップダウンメニューを作るはずなのに、セレクトボックス用の listbox/option パターンを使ってしまったのです。正しくは menu/menuitem パターンを使うべきなのですが、AI にとってこれらの使い分けはまだ難しいようです。
さらに、セマンティック要素の使い方も一貫性を欠くことがあります。トリガー部分では適切に <button> を使っているのに、メニュー項目では <div> を使ってしまうといった具合に、同じような操作なのに違う要素タイプを混在させてしまうのです。
リンターツールのよいところ
一方で、リンターツールがもたらすメリットは非常に明確でした。
もっとも印象的だったのは、指摘の具体性です。「アクセシビリティに問題があります」といった曖昧な指摘ではありません。「Elements with the "option" interactive role must be focusable」のように、何が問題で何を修正すべきかがはっきりわかります。これにより、根本原因を特定して適切な修正方向を見つけることができました。
また人間では見落としがちな細かな問題も漏れなくチェックしてくれます。開発者が「これで十分だろう」と思って見過ごしてしまうような部分でも、一貫した品質基準で検証してくれるため、品質のばらつきを防ぐことができます。
さらに興味深かったのは、1 つの警告から根本的な設計の問題に気づかせてくれることです。今回も role="option" の警告をきっかけに、設計パターン自体が間違っていることに気づき、menu/menuitem パターンへの全面的な見直しにつながりました。これは単なるエラー修正を超えて、適切な実装パターンへと導いてくれる効果といえるでしょう。
まとめ
この実験を通して見えてきたのは、AI コーディングにおけるアクセシビリティリンターの堅実な効果でした。
AI だけに頼ると、確かにアクセシビリティに配慮したコードは書いてくれます。しかし ARIA 属性の意味と実装が一致しなかったり、設計パターンを間違えてしまったりと、表面的な改善にとどまりがちです。一方でリンターツールを組み合わせると、曖昧な指摘ではなく具体的で明確な問題点を教えてくれます。これにより根本的な解決につながることがわかりました。ただし、リンターでは検出できない細かな記述の最適化は手動でのレビューが必要です。
特に印象的だったのは、1 つの警告から設計パターン全体の見直しにつながったことです。単なるエラー修正を超えて、根本的な設計の問題に気づかせてくれるのは、リンターツールならではの効果といえるでしょう。
これからのアクセシブルな UI 開発では、AI の柔軟性とリンターツールの具体性を組み合わせ、手動レビューで細部を仕上げることが、品質向上の鍵になりそうです。
今後の展望
今後は、このアプローチをより大規模な開発現場に組み込んでいく方法を考えていきたいところです。
今回の手法をより実用的にするには、Claude Code の CLAUDE.md や Hooks を使って、開発者の負担を減らしてアクセシビリティ対応を実装プロセスに組み込むことが考えられます。例えば、プロジェクト固有のアクセシビリティ要件やリンター実行手順を CLAUDE.md に記載する方法があります。またコード生成時に自動でアクセシビリティリンターを実行する Hooks を設定できます。このような仕組みにより、開発者が特別に意識しなくても、自然とアクセシブルなコードが生成される環境を作ることができそうです。
一方で、今回の実験はあくまで UI パーツ単位での検証でした。実際のプロダクト開発では、複数のパーツを組み合わせた画面単位でのアクセシビリティ対応が重要になります。例えば、フォーカス管理の順序やランドマークの構成、画面全体でのナビゲーション体験など、パーツ単位では検出できない課題があります。リンターツールがどこまで画面レベルの問題を検出できて、どこからが人間の設計判断に委ねられるのか、この境界線を明確にすることが次の重要な課題といえるでしょう。
これらの取り組みにより、だれでもアクセスし利用できるプロダクト開発を迅速かつ持続的におこなうきっかけになるでしょう。
関連リンク: