はじめに
私は実務で約2年間、フロントエンドエンジニアとしてWebアプリケーションの開発に携わっています。
現在アクセシビリティに力を入れて学んでいます。
この記事では、アクセシビリティの観点でreact-dropzone
というライブラリのコンポーネントを調べる過程で得た気づきを、自分への備忘も兼ねてまとめます。
先にこの記事のまとめを書きます。
- インタラクティブ要素に
role="presentation"
を付けるのは仕様的に避けた方が良さそう - インタラクティブな要素をネストするべきではない
react-dropzone
とは
react-dropzoneは、React向けのファイルアップロード用コンポーネントライブラリです。
ユーザーがファイルをドラッグ&ドロップでアップロードできるUIを簡単に実装できます。
疑問
検証ツールでDropzone
のDOM構造を調べると、Dropzone
の外側のdiv
要素に、role="presentation"
が付与されていました。
<div role="presentation" tabindex="0" class='dropzone'>
ソースコードを確認すると、getRootProps
という関数内で、下記のような記載があります。
props
でrole
が渡らない限り、外側のdiv
要素にはrole="presentation"
が付与されるとわかります。
role: typeof role === "string" && role !== "" ? role : "presentation",
私は、Dropzone
の外側のdiv
要素に、role="presentation"
を付与する目的がわかりませんでした。
調査
role="presentation"
とは
まずはrole="presentation"
が何かを調べました。
role="presentation"
は、WAI-ARIA ロールの一種です。
MDNには下記のように記されています。
presentation
ロールは、要素とそれに関連する子要素の意味論的意味を取り除くために使用されます。
ロールは本来、HTML要素の「意味」や「機能」をスクリーンリーダーなどの支援技術に伝えるために使用されます。
presentation
ロールを付与すると、それを打ち消すので、「この要素は何も役割を持っていない」 と伝えることになります。
例えば、見た目だけ表の罫線が欲しい時にtable
要素を使用する場合、実際には表形式でデータを表示するという意味はないので、<table role="presentation">
と指定します。
(あまりピンとこない😕)
Dropzone
の役割を考える
次に、Dropzone
の役割を考えます。
Dropzone
は、ファイルをドラッグ&ドロップでアップロードするために使用されるコンポーネントです。ユーザーがファイルをドロップできる領域が存在します。
また、react-dropzone
では、Dropzone
にフォーカスし、Enter
を押すと、ファイル選択ダイアログを開くことができます。
(ん?役割を持っているのでは?🤔)
なぜ役割があるのにも関わらず、role="presentation"
を付与して、わざわざスクリーンリーダに役割がないと伝えているのでしょうか。
Issueから歴史を紐解く
結論から言うと、Dropzone
の外側のdiv
要素に、role="presentation"
を付与している理由は、「アクセシビリティの観点から最適ではないけれど、そうせざるを得なかった」 という解釈が正しいと思います。
react-dropzone
のIssueにこんな議論があります。
元々、Dropzone
はデフォルトでrole="button"
を持っていました。そのため、その内部にさらに<button>
などのインタラクティブな要素を配置すると、インタラクティブな要素が入れ子になり、アクセシビリティ的な問題が発生すると報告があったようです。
議論の末、デフォルトのrole
をbutton
からpresentation
に書き換える変更が行われました。
このIssueから、react-dropzone
の意図を考えていきます。
インタラクティブな要素をネストしたら何が問題なのか
インタラクティブな要素とは、<button>
, <a href>
, <input>
, <select>
, <textarea>
など、ユーザーがクリックや入力、フォーカスなどの操作ができる要素を指します。
ではインタラクティブな要素をネストすることが、なぜ問題なのでしょうか。
これはHTMLの仕様が原因です。
例えばbutton
要素の場合、HTML Standardの「The 'in body' insertion mode」セクションに以下のような仕様が記載されています。
A start tag whose tag name is "button"
- If the stack of open elements has a button element in scope, then run these substeps:
1. Parse error.
2. Generate implied end tags.
3. Pop elements from the stack of open elements until a button element has been popped from the stack.- Reconstruct the active formatting elements, if any.
- Insert an HTML element for the token.
- Set the frameset-ok flag to "not ok".
(日本語訳)
ボタンというタグ名の開始タグ
- 開いている要素のスタックにbutton要素がスコープ内にある場合、次の手順を実行する:
1. パースエラーとする
2. 暗黙的な終了タグを生成する
3. button要素がスタックから取り除かれるまで、要素をスタックからポップする- 必要ならアクティブなフォーマット要素を再構築する
- このトークンに対してHTML要素を挿入する
- frameset-okフラグを「not ok」にセットする
例えば下記のようにbutton
要素をネストして書いたとします。
<button>
外側のボタン
<button>内側のボタン</button>
</button>
しかし、HTMLの仕様上、途中で外側のbutton
要素が自動的に閉じられ、実際には2つのbutton
要素が横並びで存在する構造に変換されてしまいます。
<button>
外側のボタン
</button>
<button>
内側のボタン
</button>
この仕様は、アクセシビリティの観点でも問題となります。開発者が想定した「階層」や「意味」が失われてしまうため、スクリーンリーダーやキーボード操作でバグが発生する可能性があるからです。
react-dropzone
が抱えるアクセシビリティ問題
button
要素がネストしてしまう問題を解消するために、react-dropzone
では、外側のdiv
にrole="presentation"
を付与し、役割を打ち消す対策を取りました。
これにより、確かにこの問題自体は解消されました。
しかし、この対応は完璧ではありませんでした。
Dropzone
の外側のdiv
は本来、フォーカス可能で、ファイルをドロップできる領域を伝えるという役割を持つインタラクティブな要素です。
それにも関わらず、role="presentation"
を付与してしまうと、「この領域には意味がありません」とスクリーンリーダーに伝えてしまいます。
アクセシビリティの原則は、「意味や役割を持つ要素を正しく伝えること」 です。操作可能なのに意味がない状態は、支援技術の利用者を混乱させてしまいます。
Issueにはone of the bigger accessibility crimes possible(かなり重大な“犯罪”の一つだ)
とコメントされており、厳しい評価を受けているようです😢
検証
実際に、スクリーンリーダを使って、Dropzone
がどのように読み上げられるかを検証しました。
Dropzone
内に表示されているテキストが読み上げられるため、読み上げ自体に問題はないと感じました。
最適なアクセシビリティではないけれど、使用する上では現状問題はないようです。
余談
アクセシビリティの仕様書WAI-ARIAによると、インタラクティブな要素にrole="presentation"
を付与した場合、支援技術はこれを無視して、インタラクティブな要素として認識します。
If an element with a role of presentation is focusable, or otherwise interactive, user agents MUST ignore the normal effect of the role and expose the element with implicit native semantics, in order to ensure that the element is both understandable and operable.
(日本語訳)
presentationロールが付与された要素がフォーカス可能、または他の方法でインタラクティブな場合、ユーザーに操作可能なことを示すために、支援技術はpresentationロールの効果を無視する
この仕様があるからこそ、動作上特に問題が起きていないのだと思われます。
打ち消しの打ち消しが起きているので理解しづらいですが...
終わりに
使用頻度の高いライブラリでも、アクセシビリティが完璧ではないことを知ることができました。
UIライブラリを参考に実装するときは、実装の根拠をしっかりと調べた上で、実務に反映していきたいと思います。