これは株式会社LabBase テックカレンダー Advent Calendar 2022、14日目の記事です。
13日目の記事は@hotwatermorning さんによる「WebAudio + Rust で萌え声生主になる」という記事でした。ぜひご覧ください。
アクセシビリティとは何か、なぜ必要なのか?
アクセシブルなサイトとは、ユーザーの障害の有無にかかわらずコンテンツにアクセスでき、そして可能な限り多様なユーザーが操作できる機能を持つサイトのことです。
開発者は、すべてのユーザーがキーボード、マウス、タッチスクリーンを使ってページを操作できることを前提にしがちです。その結果、一部のユーザーにとっては使い勝手がよくても、他のユーザーにとっては、煩わしいだけのものから完全に邪魔なものまで、さまざまな問題が生じる可能性があります。
Accessibility は「アクセスできる・アクセスしやすさ」と翻訳されます。Web アクセシビリティは Web にある情報やコンテンツに、あらゆる人がアクセスできようにすることを指します。
あらゆる人がアクセスできるようにするとはどういうことかというと、デバイス操作に慣れない人でも簡単に閲覧できるようにしたり、難しい言い回しを避けて理解しやすくしたり、コントラストを調整して閲覧しやすいデザインにしたり、音声読み上げをしてくれるスクリーンリーダーといった支援技術を使うために考慮するといったことです。
本稿ではReactを使用する際に考慮すべきa11y対応について紹介します。
プロジェクトのセットアップ時にしておきたいa11y対応
eslint-a11y-jsx-plugin
ESLintにはeslint-plugin-jsx-a11yというプラグインがあります。
このプラグインは、Reactアプリのアクセシビリティの問題を発見するためにJSXを静的チェックするもので、30以上のルールから構成されています。静的なコードのエラーのみを検出するので、レンダリングされたDOMのアクセシビリティをテストしたい場合は@axe-core/reactと組み合わせて使う必要があります。
axe,axe-core,react-axe
Deque System はアプリケーションの自動化された E2E アクセシビリティテストを行う axe-coreを提供しています。
The Accessibility Engineもしくはaxeは、axe-core により構築されたアクセシビリティを検査するブラウザ拡張機能です。
また@axe-core/react を使用して、開発時やデバッグ時にこれらによるアクセシビリティの検査結果を直接コンソールへ出力させることもできます。
WebAIM WAVE
Web Accessibility Evaluation Tool はアクセシビリティに関する上記とは別のブラウザ拡張機能です。
Webアクセシビリティー検証フレームワークacot
acotはアクセシビリティをテストするフレームワークです。
axeなどと同じで WCAG 2.0 や WCAG 2.1 に則った数多くのルールをベースにアクセシビリティに関する問題発見を支援してくれます。
スタートアップやベンチャー企業では、1秒でも早く、プロダクトをリリースして、事業を始められるようにしなければなりません。そんな中で後回しになりながちなアクセシビリティの改善はプロジェクト初期段階からでも最低限考慮するべきです。
ここからは実際にReactのコードを書く際に考慮すべきアクセシビリティにてついてです。
セマンティックな HTML
セマンティックなHTMLはウェブアプリケーションにおけるアクセシビリティの基礎となります。
ここでいうセマンティック (semantic) とは HTML の各要素に与えられる意味のことです。例えば、Reactコードを動くようにするために JSX に <div>
を追加すると、HTML のセマンティックが崩れることがあります。とりわけ、リスト <ol>
, <ul>
, <dl>
や <table>
タグと組み合わせるときに問題になります。そんなときは複数の要素をグループ化するために React フラグメントを使う方がよいでしょう。
以下具体例になります。
<Fragment>
<dt>りんご</dt>
<dd>赤くてまるい果物。味は甘酸っぱい</dd>
</Fragment>
もし、フラグメントタグに props を渡す必要がないのであれば、省略記法が使えます。
<>
<dt>りんご</dt>
<dd>赤くてまるい果物。味は甘酸っぱい</dd>
</>
アクセシブルなラベル付け
<input>
や <textarea>
のような各 HTML フォームコントロールには、アクセシブルな形でのラベル付けが必要です。スクリーンリーダに公開される、説明的なラベルを提供する必要があります。
React でこれらの標準的な HTML の実践知識を直接使用できますが、JSX では for
属性は htmlFor
として記述されることに注意してください。
<label htmlFor="namedInput">名前:</label>
<input id="namedInput" type="text" name="name"/>
そのほか、画像やアイコン、CSSによるスタイリングは全てののユーザーが利用できるわけではありません。それらを使用しているアプリケーションの場合は常に画像やCSSが適用されない状態を想像して、説明が必要な箇所にはこうしたアクセシブルな名前で補う必要があります。
<img src="/picture.png" alt="りんごの写真" />
// アイコンだけのボタンに "送信" と名付ける
<button aria-label="送信"><icon ... /></button>
// 画面に表示される説明がある場合は、 id で紐付けることが可能
<nav aria-labelledby="nav-title">
<h2 id="nav-title">ナビゲーション</h2>
</nav>
※JSX記法では基本的にすべての属性名がキャメルケースになりますが、ARIA属性とdata属性だけが例外でケバブケースのままになります。
フォーカス制御
React アプリケーションは実行されている間、継続的に HTML の DOM を変更するため、時にキーボードフォーカスが失われたり、予期しない要素にセットされたりすることがあります。これを修正するためには、プログラムによってキーボードフォーカスを正しい位置に移動させる必要があります。例えばモーダルウィンドウを閉じた後には、モーダルを開いたボタンにキーボードフォーカスを戻すことなどです。
下記はモーダルを閉じたタイミングでボタンにキーボードフォーカスを戻す実装例です。
const Open = () => {
const [open, setOpen] = useState(false);
const buttonRef = useRef(null);
const handleClose = () => {
setOpen(false);
buttonRef.current.focus(); // モーダルウィンドウを閉じた後にボタンにキーボードフォーカスを戻すこと
}
return (
<>
{open && (
<Modal title="タイトル" onClose={handleClose}>
こんにちは
</Modal>
)}
<button ref={buttonRef} onClick={() => setOpen(true)}>
開く
</button>
</>
);
};
上記ではonCloseに渡したコールバック関数の処理でボタンにキーボードフォーカスを戻しています。
色のコントラスト
ウェブサイトにある全ての読めるテキストが、色弱のユーザにも最大限読めるように配慮した色のコントラストがある必要があります。
適切な色の組み合わせをウェブサイト内の全てのケースについて手作業で行うのは面倒になりがちなので、代わりにアクセシブルなカラーパレット全体を Colorableなどで計算することもできます。
以下のaxe および WAVE ツールのどちらも同じように色のコントラストのテストを備えておりコントラストの違反を報告してくれます。
コントラストをチェックする能力を拡張したい場合は、以下のツールが利用できます。
id
属性
aria-controls
やaria-errormessage
、aria-labelledby
はwebページ中で重複してはならないため、一意の ID 属性で 2 つの異なる要素を紐づける必要があります。
これらの問題を解決してくれるのがReact18 で追加された新しい Hook の useId
というhooksになります。React18以前もuseOpaqueIdentifier
という名前で利用可能でしたが今後はuseId
という名前で利用できるようになりました。
例でlabelとinputの紐付けとaria-errormessageの設定、この二つの場面でidを使用したコードを紹介します。
const Sample = () => {
const [isError, setIsError] = useState(false);
const inputId = useId();
const errorMessageId = useId();
return (
<fieldset>
<div>
<label htmlFor={inputId}>サンプル</label>
<input type="text" id={inputId} aria-invalid={isError} aria-errormessage={errorMessageId} />
{isError && <p id={errorMessageId}>エラメッセージ</p>}
</div>
<button onClick={() => setIsError(!isError)}>{isError ? 'エラー文非表示' : 'エラー文表示'}</button>
</fieldset>
);
};
labelのhtmlFor
とinputのid
はuseIdによって生成したIDによって紐づけることができ、inputのaria-errormessage
はpタグ(エラー文)のidと別に生成したuseIdと紐づけることが上記で可能になります。useId
自体はとてもシンプルなhooksでクライアント・サーバー間で一意なIDを生成することでa11yを配慮することが可能です。
まとめ
Reactアプリ開発時に最低限意識すべきアクセシビリティについて紹介しました。
本稿は株式会社LabBase テックカレンダー Advent Calendar 2022の14日目記事です。明日、15日のアドベントカレンダーは@kento-tajiriさんです。よろしくお願いします。
参考記事