フォーカスの分類
アクセシビリティ・エンジニア 黒澤この記事はミツエーリンクスアドベントカレンダー2019 - Qiitaの23日目の記事です。
私はWebページの検証やコンサルティングの業務を多々行っており、開発者とやり取りすることもあります。その際、開発者に「フォーカスを受け取れるように、要素のtabindex属性に-1を設定してください」と伝えると、高い確率で「tabindex属性に-1を設定するとフォーカスを受け取らなくなるのでは?」と返されます。実際のところ、tabindex="-1"はフォーカスを受け取ります。
なぜ、そのような認識の違いが起こるかというと、ある要素がフォーカスを受け取るかどうかや、受け取るかどうかを制御する方法が複雑なためだと考えています。この記事ではその複雑性と、複雑性がどう整理されようとしているかを述べます。
フォーカスを受け取る経路
Webページでは要素がフォーカスを受け取る経路は、ユーザーの利便性や歴史的経緯を踏まえてかなりの種類があります。例えば、次のようなものがありますが、他にもまだまだあります。
- Tabキーを押した場合
- (フォームコントロールやリンクなどの)要素をクリックした場合
- label要素をクリックした場合(対応するフォームコントロールにフォーカスが移動)
- #fragmentに遷移した場合(#fragmentにフォーカスが移動)
- JavaScriptでfocus()メソッドを呼び出した場合
要素をクリックしてフォーカスが移動することは一見不自然に見えるかもしれませんが、マウスを使っているユーザーにとって必須ともいえる挙動です。もし、マウスで要素をクリックしてもフォーカスが移動しないなら、テキスト入力欄をクリックしてもフォーカスは移動せず、ユーザーは文字を入力することはできません。マウスユーザーもTabキーを押してフォーカスを移動しなくてはならないはずです。
さて、問題なのはフォーカスを移動させる経路が複数あることだけではなく、その経路が機能するかどうかが個別に制御されていることです。例えば、tabindex="-1"を指定すると、Tabキーを押してもフォーカスを受け取りませんが、他の経路では受け取ります。
<button type="button" tabindex="-1">ボタン</button>
<label>入力欄<input tabindex="-1"></label>
この例ではいずれもTabキーを押してもフォーカスは移動しませんが、クリックするとフォーカスは移動しますし、focus()メソッドを呼び出してもフォーカスは移動します。もっとも、input要素にtabindex="-1"を指定することはまずありませんが、WAI-ARIA Authoring Practices 1.1のMenuのように矢印キーで項目を選ぶUIを実装する場合などはtabindex="-1"を適切な要素に指定する必要があります。矢印キーで項目を選ぶUIは、Tabキーを押してもフォーカスが移動しないようにした上で、矢印キーが押されたことをJavaScriptで検出してfocus()メソッドでフォーカスを移動させているためです。
その一方で、フォーカスを全く受け取らない要素も存在します。
- 無効なフォームコントロール(disabled)
- 表示されていない要素(display: noneなど)
- dialog要素が表示されているとき、dialog要素とその子孫以外の要素(inert)
これらの要素はtabindex属性を指定してもフォーカスを受け取りません。
<button type="button" disabled tabindex="0">ボタン</button>
<label>入力欄<input disabled tabindex="0"></label>
このような複雑な挙動・仕組みになっているのは、歴史的な経緯としかいいようがなく、初学者にとって分かりやすいものではありません。
仕様におけるフォーカスの整理
2019年はフォーカス関連の仕様が整理されました(注1)。HTML仕様ではフォーカスを受け取るかどうかが次のように整理されています。
フォーカス可能は理論上フォーカス可能であることを指します。フォーカス可能な要素は少なくともJavaScriptのfocus()メソッドでフォーカスを移動することができます。フォーカス可能でなければ、何をしてもフォーカスは移動しません。前述の無効なフォームコントロールは、そもそもフォーカス可能ではないのです(注2)。
逐次フォーカス可能はTabキーを押した場合にフォーカスが移動することを指します。フォームコントロールやリンクは基本的には逐次フォーカス可能です。div要素などの逐次フォーカス可能でない要素もtabindex属性に0を指定すると逐次フォーカス可能になります(Tabキーを押すとフォーカスが移動します)。逆に、逐次フォーカス可能であってもtabindex属性に-1を指定すると逐次フォーカス可能ではなくなります(Tabキーを押してもフォーカスは移動しません)。なお、逐次フォーカス可能な要素はフォーカス可能です。
クリックフォーカス可能は要素をクリックするとフォーカスが移動することを指します。フォームコントロールやリンクは基本的にはクリックフォーカス可能です。div要素などのクリックフォーカス可能でない要素もtabindex属性に-1を指定するとクリックフォーカス可能になりますし、0を指定してもクリックフォーカス可能になります。tabindex="-1"は要素をクリックフォーカス可能にする指定であるため、フォームコントロールやリンクにtabindex="-1"を指定してもクリックフォーカス可能なままです(クリックするとフォーカスが移動します)。tabindex属性を使ってフォームコントロールやリンクをクリックフォーカス不可能にすることはできません。なお、クリックフォーカス可能な要素はフォーカス可能です。
おわりに
いかがでしょうか。これまで漠然としていたフォーカスに少しは見通しがついたでしょうか。
実際のところ、これでも未整理の問題は多々あります(注3)。ただ、それらの問題も徐々に整理されようとしています。皆さんの開発がより一層シンプルになるようサンタさんにお願いして、筆をおきます。
注
注1:2019年にフォーカス関連の仕様が整理されたのは、カスタム要素やShadow DOMでのフォーカスの扱いを定義するためであり、初学者の理解のためではありません。とはいえ、結果的に初学者にとっても分かりやすくなりました。HTML仕様におけるフォーカスの整理例にはGitHubのプルリクエスト 4768 - Define different types of "focusable" & remove "tabindex focus flagがあります。
注2:無効なフォームコントロールがフォーカス可能でないことはHTML仕様の6.5.2に記載があります。
注3:HTML仕様におけるフォーカス関連の問題はGitHubの課題 4607 - Focus meta-bugにまとまっています。