概要
アクセシビリティのチェックポイントとして、
ユーザーが操作するコンポーネントを適切なタグで実装するか、
適切に属性を設定する必要があるというものがあります。
create-next-appで作成したばかりのプロジェクトでは、
divタグなど非対話型コンテンツにonClickが設定されただけの場合、ESLintで特に指摘されません。
また、StorybookとAccessibility addonでコンポーネントをチェックしても指摘されず、
そのまま実装を進めてしまいがちです。
最近自分もこの事に気付いてコードレビューで指摘される前に検知・改善できないか調査しました。
この記事ではESLintルールを変更して、静的解析で適切なタグが利用できてないことを検知する設定を紹介します。
(eslint-plugin-jsx-a11yを推奨ルールで利用していると記事内のルールが適用済みのはずです)
ご自身のプロジェクトにてルール設定を見直してみるきっかけになれば幸いです。
デフォルトの設定を検証する
検証に利用したツール
- Next.js: v14.0.3
- React.js: v18.0.2
- Storybook: v7.6.1
- storybook-addon-a11y: v7.6.1
検証用のプロジェクトとコンポーネントを用意
まず、以下の流れでプロジェクトを準備します。
-
npx create-next-app@latest
でNext.jsプロジェクトを作成 -
npx storybook@latest init
でStorybookをインストール - storybook-addon-a11yをインストールして有効化
コンポーネントをチェックする
Storybookのinitコマンドを実行するとサンプルでボタンコンポーネントが追加されるので
これをbuttonタグからdivタグに変更してみます。
"use client";
import React from "react";
import "./button.css";
interface ButtonProps {
/**
* Is this the principal call to action on the page?
*/
primary?: boolean;
/**
* What background color to use
*/
backgroundColor?: string;
/**
* How large should the button be?
*/
size?: "small" | "medium" | "large";
/**
* Button contents
*/
label: string;
/**
* Optional click handler
*/
onClick?: () => void;
}
/**
* Primary UI component for user interaction
*/
// NOTE: 元のコードではスプレッド構文でpropsを受け取っていましたが変更しています
// スプレッド構文だとonClickなどが設定されているか検知できなくなります
// propsでのスプレッド構文利用の是非はこの記事の範囲外なので詳細は割愛します
export const Button = ({
primary = false,
size = "medium",
backgroundColor,
label,
onClick,
}: ButtonProps) => {
const mode = primary
? "storybook-button--primary"
: "storybook-button--secondary";
return (
// NOTE: buttonからdivに変更し、type propsを削除
<div
className={["storybook-button", `storybook-button--${size}`, mode].join(
" "
)}
onClick={onClick}
>
{label}
<style jsx>{`
button {
background-color: ${backgroundColor};
}
`}</style>
</div>
);
};
VS Code上では特にエラーは出ていません。
StorybookのAccessibility addonでもコントラスト不足の指摘のみとなっています。
eslint-plugin-jsx-a11yのルールを変更する
今回のような実装はeslint-plugin-jsx-a11yのjsx-a11y/no-static-element-interactionsを有効化することで検知可能です。
Next.jsのプロジェクトではeslint-plugin-jsx-a11yのセットアップがされているので
.eslintrc.jsonを編集し、プラグインのREADMEで推奨されている内容でルール追加を行いました。
{
"extends": ["next/core-web-vitals", "plugin:storybook/recommended"],
"rules": {
"jsx-a11y/no-static-element-interactions": [
"error",
{
"handlers": [
"onClick",
"onMouseDown",
"onMouseUp",
"onKeyPress",
"onKeyDown",
"onKeyUp"
],
"allowExpressionValues": true
}
]
}
}
VS Codeで指摘されるようになりました。
ちょっとした補足
eslint-plugin-jsx-a11yとStorybookのAccessibility addonはそれぞれチェック対象やルールが異なります。
2つのツールを利用することで、より多くのフィードバックが得られます。
web.devでも同様にeslint-plugin-jsx-a11yとレンダリングされたDOMのテストツールを組み合わせる記事があります。
Accessibility auditing with react-axe and eslint-plugin-jsx-a11y
Next.jsのデフォルト設定は以下のリンクで確認できますが、
いくつかのjsx-a11yルールが適用されているのでno-static-element-interactionsが有効になっていないのにも理由があるかもしれません。(深掘りはできてないです)
https://github.com/vercel/next.js/blob/canary/packages/eslint-config-next/index.js
StorybookのAccessibility addonはaxe-coreを利用しているので
チェックできるルールは以下のリンクで確認できます。
https://github.com/dequelabs/axe-core/blob/master/doc/rule-descriptions.md
例えばbuttonタグの中にaタグがある場合など、
対話型コンテンツがネストしているとStorybook側で指摘されます。
まとめ
この記事ではeslint-plugin-jsx-a11yとStorybookのAccessibility addonを組み合わせて
ユーザーが操作するコンポーネントを適切なタグでマークアップできるよう静的解析で検知することを紹介しました。
それぞれのプロジェクトで設定したアクセシビリティ目標を達成できるように
コードとともにチェックツールの設定なども改善していけるとよさそうです。