2026年前期のアクセシビリティ系OSS活動まとめ
この記事は、CYBOZU SUMMER BLOG FES ‘26の記事です。
こんにちは、フロントエンドエンジニアのmehm8128です。 最近以下のような、AIによるアクセシブルでないコードの負の学習サイクルが問題視されています。
- AIがアクセシブルでないコードを学習する
- アクセシビリティにあまり詳しくない人が、AIを使ってアクセシブルでないコードを書く
- それによってアクセシブルでないコードを学習したAIが、さらにアクセシブルでないコードを書くようになっていく
関連する話題が、先日のCA11Y #4でも話されていました。 AI はアクセシビリティを向上させられるのだろうか? (2026-06-20 @ ca11y) | PPTX
これを改善するための1つの手段として、僕はBiome、Oxlintのようなlinterや、axe-coreのようなアクセシビリティチェックツールへのcontributionを行っています。
このようなツールで分析できる観点は限られていますが、アクセシビリティにあまり詳しくない人は考慮できていなかったポイントを知って改善でき、詳しい人でも見逃しを防ぐことができます。
それぞれのツールへのcontributionの詳細を紹介していきます。
Oxlint #
ESLintのRust移植であるOxlintに、10件ほどPRを出しました。 Oxlintでは、いくつかのESLintのプラグインにあるルールをRustで実装しており、移植用のissueが作成されます。そこに未実装のルールがリストアップされるので、自由に実装してPRを作成できます。 例えばアクセシビリティのルールは、以下のissueでeslint-plugin-jsx-a11yから移植していました。
eslint-plugin-jsx-a11yは既に全てのルールが実装済みですが、アクセシビリティ以外だと例えばeslint-plugin-jsdocやeslint-plugin-nのようなプラグインには、まだ未実装のルールが多くあります。
僕はこの移植自体の作業を3件、既存のルールで修正が必要そうな箇所の修正を7件、PRとして提出しました。それぞれ軽く紹介します。
移植作業 #
移植自体の作業をしたPRは以下の3つです。
- feat(linter): implement aria/proptypes #17253
- feat(linter): implement interactive-supports-focus #21767
- feat(linter/jsx-a11y): implement control-has-associated-label #21985
実は前にBiomeでも同じような移植作業を行っていたことがあったので、実装方法自体はあまり変わらず、そこまで詰まることなく実装できました。
Biomeと違う点として、ESLintプラグインの元ルールに実装されているテストケースをそのまま取ってきて、基本的に全て通過するようにしてPRを提出する必要があります。Biomeでは、必ずしも元ルールから取ってくる必要はなくて自分で一から書くこともできました。他に、アクセシビリティルール用の汎用utilメソッド(後述)の差異についてもBiomeとOxlintの大きく違う点です。
既存ルールの修正 #
こちらの方がたくさんあります。主なものを挙げますが、他に軽微な修正やリファクタPRをいくつか出しています。
- feat(linter): extends
no-redundant-rolesandprefer-tag-over-rolesupport roles #22069 - fix(linter): fix role-has-required-aria-props #22097
- fix(linter): add more expression support for iframe-has-title #22460
- fix(linter): allow dialogs and popovers for no_autofocus #22289
issueに挙げられていたものの修正や、自分で立てたissueの修正をしています。移植系のルールということで様々な人がPRを作成しているため考慮漏れがあったり、ESLintの元ルールのテストケースが不足していて、網羅するべきARIA属性やロールが網羅できていないというケースもありました。
そのため、AIを使って以下の手順でOxlintとESLintの差異をまとめてもらい、必要に応じてissue化しました。
- アクセシビリティのlintエラーが検出されるようなplaygroundを作る
- OxlintとESLintでそれぞれアクセシビリティのルールを全て有効にする
- OxlintではエラーになるけどESLintではエラーにならないもの、もしくはその逆を洗い出してMDにまとめる
自分ですぐに直せるものは前述のようなPRを作り、issue化だけして寝て、朝起きたら他の人がPRを作ってくれていたことがありました。
- linter:
no-autofocus—autoFocus={false}should not be flagged #21912 - linter:
media-has-caption— Rule fires twice for the same violation #21911 - linter:
prefer-tag-over-role— Missing support for several roles #21910
このようにAIを使ってplaygroundで差異を検知する方法は、全部を完全に検知できるわけではありません。しかし、今回のようなESLintのルールのOxlintへの移植作業や、Oxlintを使い始めるときにESLintからのmigrationの動作確認にも使えると思います。
utilメソッドの差異について #
前述したBiomeとOxlintのutilメソッドの話です。
これらのlinterでは、ロールやaria-属性を扱うためにutilメソッドを共通化して持っており、様々なルールで再利用可能にしています。
Biomeでは一部をARIAの仕様書から自動生成しているのですが、Oxlintでは全て手動で参照してハードコーディングしています。それに加え、Biomeではロールに対して利用可能なaria-属性の値を比較的厳密に条件分岐して計算しているのですが、Oxlintではまだそこまでできておらず、差分が生じています。
Biome: crates/biome_aria_metadata/src/lib.rs Oxlint: crates/oxc_linter/src/utils/react.rs
僕のPRでは特にこのutilメソッドを修正することが多かったのですが、仕様書を照らし合わせる作業が多く、条件分岐が発生する箇所はある程度妥協している部分もあります。 また、元となっているESLintプラグインではA11yance/aria-queryに加えて、A11yance/axobject-queryも参照してロールに関する計算をしています。これはARIAの仕様書には存在しない、Chromeの内部実装におけるロールの情報を取ってきているライブラリなのですが、現在はメンテされておらず、READMEにあるChromiumのリンクが切れていたりして、あまり信用できる情報ではなさそうという問題もあります。前述のように、基本的には元ルールのテストが通るように実装しなければならないので、これに依存していると辛いです。
複数ライブラリ間でロールやaria-属性の情報の取得を再実装しなくて済むようにする方法として個人的に気になっているのが、w3c/ariaのリポジトリに含まれているroleInfo.jsです。
これは同じディレクトリのaria.jsなどのスクリプトを用いて、ロールの情報をJSオブジェクトとして仕様書から自動生成して定義しているファイルです。linterで必要になる情報を全て含んでいるわけではないですが、仕様書のリポジトリに含まれているということで信頼できる情報となっています。
HTML要素とARIAロールとの関係や、ロールがabstractロールかどうか、aria-属性の値の形式(aria/proptypesの実装で追加したようなマッピング)などは含まれていないようです。
関連して、試験的なESLintプラグインmehm8128/eslint-plugin-a11y-experimentalをAIを用いて個人的に作っています。aria-actionsやfocusgroupなど比較的新しいWeb技術のアクセシビリティに関するルールを含んでいます。このプラグインではroleInfo.jsを使っており、例えば祖先にwidgetロールを含むロールをインタラクティブなロールとして扱う処理はparentRolesを参照して実装しています。
axe-core #
axe-coreはaxe DevToolsやLighthouse、@storybook/addon-a11y、nuxt/a11yなどの内部で利用されている、アクセシビリティチェックツールです。BiomeやOxlintとは違い、実際にWeb上に表示されているDOMの状態をチェックできます。
linterとは少し違って今まで触れたことがなかったので、比較的簡単そうなものを探して2つほど試しにPRを出してみました。
例えばfix(label-content-name-mismatch): match visible text with aria-label and exclude invisible text #5096は、スクリーンリーダーで読み上げられる情報が、可視テキストと比べて少ないときにエラーになるルールの修正です。
今までは誤ってalt属性やSVGの<title>要素なども可視テキストとして計算されてしまっていたのを、計算に使う関数を変更してそれらのテキストは含まれないように修正しました。
issueは眺めるだけでも学びになりそうなものがたくさんありました。最近話題のHTML in Canvasサポートの議論などもあるようです。他には、最近の傾向としてElementInternalsのサポートに関するissueが多く動いている印象です。
また、Xで「axe DevToolsのチェック結果が、前まで日本語だったのに英語になってしまった」という投稿を見たのでissueを立てたところ、3週間ほどで直してもらえました。axe DevTools自体のコードは非公開なのですが、issueのテンプレートの”Product”の欄で”axe Extension”を選べばOKでした。
おまけ1: NVDA #
Windows向けスクリーンリーダーであるNVDAにも、1つバグ修正PRを出しました。
普段動作確認などで使っているスクリーンリーダーなので、仕組みを知っておきたくてやってみました。
今まではrole="alert"の要素が表示されたときに、accessible name/descriptionもしくは、その要素に含まれているインタラクティブな要素しか読み上げませんでした。しかし、見出し要素やリスト要素、<p>要素といったインタラクティブでない要素も読み上げてほしいという要望があり、それを実現しました。
以前nameFrom: headingとsectionheader/sectionfooterについてやAccessibility APIでブラウザから情報を取得してみるで内部実装を少し読んでいたため、修正が必要な箇所を見つけるのにはそこまで時間がかかりませんでした。
ウェブアクセシビリティ基盤委員会(WAIC)にてサイボウズメンバーはこんな活動をしています - Cybozu Inside Outで書いたように、スクリーンリーダーのWeb技術に対するサポート状況を改善するために、NVDAに直接貢献するという方法は有効だと思うので、今後も少しずつ貢献していきたいです。
おまけ2: Storybook #
Storybookでは現状、<html>要素のlang属性が固定でenになっています。これを書き換えるにはdocument.documentElement.setAttribute("lang", "ja")のように後からJSで上書きしなければなりません。
デジタル庁のStorybookの例: design-system-example-components-react/.storybook/preview-head.html
公式にオプションがないので、この方法を知らないと諦めてしまったり、そもそもlang属性をStorybook上の言語に合わせないといけないことを見逃してしまったりという問題があります。さらに、word-breakやhyphensなどlang属性の値によって挙動が変わるCSSは、issueのコメントで述べられているように、HTMLの初回読み込み時のlang属性しか見ないので後からJSで上書きしても効かないという問題を抱えているようです。
そこで、公式でlang属性を変更できるようなオプションを生やそうとしたのが以下のPRです。
最初はこのまま進みそうだったのですが、より細かくlang属性を制御したいという話になり、僕のPRを参考にしてメンテナーの方に別でPRを作ってもらう方針になりました。
v10.5.0から利用可能になる予定です。ぜひ利用してみてください。
まとめ #
これらのOSSは多くの開発者に使われています。axe-coreやNVDAはアクセシビリティに興味のある人に多く使われていますが、それ以外のOSSはアクセシビリティに興味があるかどうかに関係なく、使われることが多いです。 このような、ユーザーの多いOSSに対してインパクトの大きい改善を入れることで、今後もアクセシビリティの分野で貢献していきたいです。