jiyuujin is united with the people of Ukraine and the international community.

当記事は公開されてから 1 年以上経過している記事です。

「eslint-plugin-jsx-a11y を導入する」の改善提案など気軽にご協力いただければ幸いです。

Issue はこちらでよろしくお願いします: https://github.com/jiyuujin/webneko-blog/issues

eslint-plugin-jsx-a11y を導入する
2/3/2022
A11Y
React
TypeScript

eslint-plugin-jsx-a11y を導入する

当方 React (CRA/Vite) のプロジェクトにおけるアクセシビリティを考慮するため eslint-plugin-jsx-a11y をインストールすることとした。

# npm
npm i -D eslint-plugin-jsx-a11y

# yarn
yarn add -D eslint-plugin-jsx-a11y

実際に導入する

下記のように .eslintrc.jsextendsplugins に設定する。

module.exports = {
  extends: ['plugin:jsx-a11y/recommended'],
  plugins: ['jsx-a11y'],
}

ルールセット

ルールセットは 2 種類ある。

  • jsx-a11y/recommended
  • jsx-a11y/strict

基本的にどちらのルールセットを使っても構わない。

対応ルール全てエラーと認識してくれる一方、一部ルールではエラーと見做さないケースも設定できたりなど、弾力的に運用できる。

まず、マウスやキーイベントのリスナーなどに role を付ける。

該当のルールは下記の通りです。

次に、非対話型の HTML 要素や WAI-ARIA ロールはマウスやキーイベントのハンドラーをサポートしないので role を付ける。

非対話型の HTML 要素

  • <main>
  • <area>
  • <h1>
  • <h2>
  • <h3>
  • <h4>
  • <h5>
  • <h6>
  • <p>
  • <img>
  • <li>
  • <ul>
  • <ol>

非対話型の WAI-ARIA ロール

  • <article>
  • <banner>
  • <complementary>
  • <img>
  • <listitem>
  • <main>
  • <region>
  • <tooltip>

該当のルールは下記の通りです。

最後に th 要素のみ scope を許容する。

該当のルールは scope です。

あらゆる warning を解決する

eslint-plugin-jsx-a11y を読み込んで eslint --fix してみる。

# eslint
npx eslint . --ext ts,tsx --fix

修正すべき warning に遭遇しなければ、導入完了とみて問題無いだろう。しかし、いくつかの warning に遭遇することがある。

具体的には div などに代表される静的な HTML 要素で onClick イベントを使ってしまっているケースなどが挙げられる。

jsx-a11y/click-events-have-key-events

onClick イベントを使う場合、マウスを使用できないユーザのために onKeyUp / onKeyDown / onKeyPress を考慮する必要がある。

もちろん 1 つも無ければ、アクセシビリティ的にアウトだ。

const Component = () => (
  <div onClick={() => {}}>
    {/* 何らかのコンテンツ */}
  </div>

)

なお、広くボタン用途に使われる button 要素で onClick イベントを使用した方が良い。

この button 要素では既に onKeyUp / onKeyDown / onKeyPress が考慮されている。

const Component = () => (
  <button onClick={() => {}}>
    {/* 何らかのコンテンツ */}
  </button>
)

jsx-a11y/no-static-element-interactions

div など静的な HTML 要素でマウスやキーイベントを設定する場合に、要素の role 属性を設定する必要がある。

const Component = () => (
  <div onClick={() => {}} role="button">
    {/* 何らかのコンテンツ */}
  </div>
)

onClick イベントと合わせて role 属性を設定しても特に問題は無い。

ただし、広くボタン用途で使われる button 要素に変更する選択肢を取った方が良い。

const Component = () => (
  <button onClick={() => {}}>
    {/* 何らかのコンテンツ */}
  </button>
)

最後に

とあるプロジェクトで ESLint を設定した当初 70 近い warning を観測した。

✘ 68 problems (68 errors, 0 warnings)

Errors:
  29  jsx-a11y/click-events-have-key-events
  28  jsx-a11y/no-static-element-interactions
   5  jsx-a11y/aria-role
   2  jsx-a11y/media-has-caption
   2  jsx-a11y/anchor-is-valid
   1  jsx-a11y/no-autofocus
   1  jsx-a11y/no-noninteractive-element-interactions

しかし、そのどれもが同じようなエラーであるため、そこまで怖がる必要は無い。

アクセシビリティの ESLint に伴う --fix をビルド時に強制したことで、最低限の品質を担保させるようにした。

その他

eslint-plugin-jsx-a11y でサポートされているルール一覧です。

視覚によるチェックでは確認しきれない部分を補うため、コードベースでコンポーネントごとに自動チェックする。

Rule Recommended Strict
alt-text error error
anchor-has-content error error
anchor-is-valid error error
aria-activedescendant-has-tabindex error error
aria-props error error
aria-proptypes error error
aria-role error error
aria-unsupported-elements error error
autocomplete-valid error error
click-events-have-key-events error error
heading-has-content error error
html-has-lang error error
iframe-has-title error error
img-redundant-alt error error
interactive-supports-focus error error
label-has-associated-control error error
media-has-caption error error
mouse-events-have-key-events error error
no-access-key error error
no-autofocus error error
no-distracting-elements error error
no-interactive-element-to-noninteractive-role error, with options error
no-noninteractive-element-interactions error, with options error
no-noninteractive-element-to-interactive-role error, with options error
no-noninteractive-tabindex error, with options error
no-onchange error error
no-redundant-roles error error
no-static-element-interactions error, with options error
role-has-required-aria-props error error
role-supports-aria-props error error
scope error, with options error
tabindex-no-positive error error
(12056 characters)

あわせてよみたい..