「アクセシビリティ対応は大事」と分かっていても、リリース直前に監査が後回しになる現場は本当に多いです。私自身、個人プロジェクトを4本同時に動かしている中で、a11y(アクセシビリティ)チェックだけは何度も流してしまい、結果として後追いで地味な修正コストを払う羽目になりました。
ここではClaude Codeのスキルとフックを組み合わせて、「監査を後回しにできない仕組み」を作る具体的な手順を共有します。axe-coreというデファクトスタンダードのa11yランタイムを軸に、日常のプルリク作成のたびに自動でコメントが付く流れまで持っていきます。
なぜ「監査ツールを入れた」だけでは続かないのか
アクセシビリティ監査が形骸化する原因は、ツールの不足ではありません。ほとんどのプロジェクトはすでに何らかのlinterやテストを動かしています。問題は、結果が「読まれる場所」にないことです。
CIのログに警告が並んでも、PR画面を開いている人は気づきません。レポートをSlackに飛ばしても、すぐに別の通知に押し流されます。私の経験では、監査結果がプルリクのコメント欄に直接書かれるようになって初めて、毎回目に入る情報になりました。
Claude Codeを噛ませる狙いは2つあります。1つ目は、生のaxe-core出力をそのままぶつけるのではなく、「修正に必要な手数」を要約した日本語コメントに変換すること。2つ目は、フックとスキルを組み合わせて、ローカル開発中もコミット直前に同じチェックが走るようにすることです。
ステップ1: axe-coreをCLIから動かす土台を作る
最初にやることは、ブラウザを立ち上げずにaxe-coreを動かす環境の用意です。Playwrightを噛ませるパターンが一番安定します。
# 必要パッケージのインストール
npm install -D @axe-core/playwright playwright @playwright/test
npx playwright install chromium次に、監査スクリプトを1つだけ用意します。これは「指定したURLをaxe-coreで走査してJSON出力する」だけのシンプルな役割に絞ります。複雑にすると保守できなくなるので、ここは敢えて素朴に書きます。
// scripts/a11y-audit.mjs
import { chromium } from 'playwright';
import AxeBuilder from '@axe-core/playwright';
import { writeFileSync } from 'node:fs';
const targets = process.argv.slice(2);
if (targets.length === 0) {
console.error('Usage: node scripts/a11y-audit.mjs <url> [<url> ...]');
process.exit(1);
}
const browser = await chromium.launch();
const page = await browser.newPage();
const results = [];
for (const url of targets) {
await page.goto(url, { waitUntil: 'networkidle' });
const r = await new AxeBuilder({ page })
.withTags(['wcag2a', 'wcag2aa']) // まずはAA基準まで
.analyze();
results.push({ url, violations: r.violations });
}
await browser.close();
writeFileSync('a11y-report.json', JSON.stringify(results, null, 2));
console.log(`Found ${results.reduce((n, r) => n + r.violations.length, 0)} violations across ${results.length} pages.`);期待する出力は、プロジェクト直下に a11y-report.json が生成されること、そして違反件数のサマリが標準出力に出ることです。CIでもローカルでもこのスクリプト1つで完結するようにしておくのが、後で運用が壊れにくいコツです。
ステップ2: Claude Code用のスキルとして登録する
次に、このスクリプトをClaude Codeから呼び出せるスキルにします。Claude Codeのスキルは「ある決まった目的のためにモデルが自律的に呼び出せる手順書」です。詳しい仕組みはClaude Codeカスタムスキル開発ガイドで紹介していますが、ここでは最小構成だけ書きます。
# .claude/skills/a11y-audit/SKILL.md
---
name: a11y-audit
description: "指定URLにaxe-coreでアクセシビリティ監査を実行し、違反を重要度順に要約する。トリガー: アクセシビリティ, a11y, 監査"
---
# 使い方
1. `npm run dev` が動いていることを確認する
2. `node scripts/a11y-audit.mjs http://localhost:3000` を実行する
3. 生成された `a11y-report.json` から違反を critical / serious / moderate / minor に仕分ける
4. critical と serious を最優先にし、各違反について「該当セレクタ」「WCAG項目」「修正方針」を1つずつ提示する
5. PR本文に貼れるMarkdownブロックとして整形するポイントは2つあります。1つはdescriptionに「トリガー」のキーワードを必ず含めること。これがないとClaude Codeはスキルを呼び出し判断ができません。もう1つは、最小限の手順だけを書き、判断のロジック(どれを「重要」とみなすか)はモデルに任せることです。スキルを書きすぎると、現場ごとの差異に対応できなくなります。
ステップ3: PreCommitフックで「漏れ」を防ぐ
ここまででローカル監査は走るようになりますが、コミット前に思い出して走らせるのは現実的ではありません。Claude Codeのフックを使って、コミットの直前に自動で監査を発火させます。フックの仕組み自体はClaude Code Hooks自動化ガイドで詳しく解説しているので、ここでは設定だけ載せます。
// .claude/settings.json
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash(git commit*)",
"command": "node scripts/a11y-audit.mjs http://localhost:3000 || true",
"description": "コミット前にa11y監査を実行(失敗してもコミット自体は止めない)"
}
]
}
}|| trueを付けて意図的に「コミットを止めない」設計にしている理由は、a11y違反でコミットが詰まると開発者がフックを無効化してしまうからです。フックは罰ではなく、目に留まる仕組みとして設計するほうが、結局は長期的に効きます。違反があるときはClaude Code側でレポートが要約され、開発者の前に提示される。それで十分です。
ステップ4: GitHub ActionsでPRに自動コメントする
最後のピースは、PRが立ったタイミングで自動コメントを残す仕組みです。Claude Codeとの連携はClaude Code GitHub Actions自動化ワークフローで扱っていますが、ここではaxe-core部分だけ抜き出します。
# .github/workflows/a11y.yml
name: A11y Audit
on:
pull_request:
branches: [main]
jobs:
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20 }
- run: npm ci
- run: npx playwright install --with-deps chromium
- run: npm run build && npm run start &
- run: sleep 5 && node scripts/a11y-audit.mjs http://localhost:3000
- name: Comment summary on PR
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const data = JSON.parse(fs.readFileSync('a11y-report.json', 'utf8'));
const counts = data.flatMap(p => p.violations).reduce((acc, v) => {
acc[v.impact] = (acc[v.impact] || 0) + 1;
return acc;
}, {});
const body = [
'## アクセシビリティ監査の結果',
`- critical: ${counts.critical || 0}`,
`- serious: ${counts.serious || 0}`,
`- moderate: ${counts.moderate || 0}`,
`- minor: ${counts.minor || 0}`,
'',
counts.critical || counts.serious
? '⚠️ critical / serious が残っています。マージ前に対応をお願いします。'
: '✅ critical / serious レベルの違反はありません。',
].join('\n');
await github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body
});このワークフローのよいところは、a11y-report.json がそのままアーティファクトとして残せることです。違反の生データを Claude Code から後で読ませて、リファクタの優先順位を決めるのにも使えます。
ありがちな落とし穴
実際に運用してみて引っかかったポイントを3つだけ共有します。どれも「最初に知っていれば30分は節約できた」というレベルのものです。
1つ目は、SPA のルーティングを正しく扱えないケースです。Playwright で goto() した直後に axe-core を回すと、クライアント側のレンダリングが終わる前に走査が始まり、誤検知が出ます。waitUntil: 'networkidle' を入れるだけで解消するので、最初から書いておくのが安全です。
2つ目は、CIランタイムの増加です。Playwrightのブラウザバイナリだけで200MB近くあり、毎回ダウンロードしているとCIが遅くなります。actions/cache@v4 でPlaywrightのキャッシュディレクトリを保存しておくと、2回目以降は劇的に速くなります。
3つ目は、minor / moderate の違反まで全部出すと圧倒される問題です。最初は critical / serious だけに絞ってコメントを出し、慣れてきてから段階的にレベルを下げるほうが、結果的に修正が進みます。これは linter 全般に言える話で、Claude CodeでESLint修正を安全に進めるワークフローでも触れている考え方と通じます。
監査を「個人の意識」から「仕組み」へ
アクセシビリティは、個人の意識に頼ると必ず後回しになります。逆に、目に入る場所に結果が出てくる仕組みさえ作れば、半年後の自分が同じ違反を繰り返すことはなくなります。
私が今日伝えたかったのは、「監査ツールを入れる」と「監査を運用に乗せる」の間には大きな差があるということです。Claude Code はその差を埋めるためのとても素直な道具で、フック・スキル・GitHub Actions のどれかひとつでも導入してみると、感触がつかめると思います。
まずは scripts/a11y-audit.mjs だけでいいので、あなたのプロジェクトに置いてみてください。5分で動くはずです。動いたら、次は PreCommit フックを足して、最後に GitHub Actions に持っていく。この順番だと挫折しにくいです。
書籍で体系的に