注目の視認性を高める - focus-within それとも has(:focus)?
以前は、アクセシビリティを向上させるために、CSSの:focus
や:focus-visible
疑似クラスを使ってフォーカスモードの要素のスタイリング方法を見てきました。しかし、子要素がフォーカスされている際に親要素をスタイリングし、混雑したページ上でユーザーの目立たせたい場合はどうすればいいでしょうか?そのために私たちが使えるオプションには何があるでしょうか?
この記事で:focus-within
疑似クラスから始めてみましょう。
目次
:focus-within疑似クラスを使用する
:focus
疑似クラスはフォーカスモードの要素を選択するためのものですが、:focus-within
はその要素の祖先をDOMツリーで選択するためのものです。つまり、:focus-within
は、その中のあるネストされた要素にフォーカスされている(:focus
と一致する)すべての要素にマッチします。
以下に検索ボックスの例を示します。検索ボックスには2つのレベルのネストがあります。1つ目のレベルはdiv
で、クラス名はsearch-container
です。そして2つ目はlabel
要素です。
<div class="search-container">
<label>
Search a title
<input
type="text"
placeholder="Search"
id="search-box"
/>
</label>
<div>
<button class="clear-btn">Clear</button>
<button class="search-btn">Search</button>
</div>
</div>
それから、search-container
クラスの付いた最上位のdiv
に:focus-within
のアウトラインスタイルを追加し、label
要素の:focus-within
にフォントの太さを加えます。
.search-container:focus-within {
outline: 2px solid rgba(66, 153, 225, 0.5);
outline-offset: 5px;
}
label:focus-within {
font-weight: 600;
}
これで、label
内のinput
要素にフォーカスしたとき、ラベルのテキストが太字になり、クラスsearch-container
の最上位div
のアウトラインが見えるようになります。また、ボタンにフォーカスすると(クリックするかTabキーを使って)、最上位のdiv
のアウトラインスタイルだけが変わるのがわかります。
異なるスタイルがフォーカス時に入力に、およびボタンにフォーカスしたときに適用される
:focus-within
のスタイルをデバッグする際には、DevToolsへ行って、目的の要素を調査し、Styles
タブの:hov
セクションにリストされている要素の:focus-within
状態をトグルします。同じアプローチに従って子要素の:focus
状態もトグルし、親のスタイルがどのように変化するかを見てみましょう。
:focus-within
疑似クラスが理解できたので、次にhas()
疑似クラスで同じ結果を達成できるかどうか見てみましょう。
focus withinのための:has()疑似クラスの使用
:has()
は、リストの相対セレクタを入力として受け付ける関数型疑似クラスです。それにより、与えられたセレクタリストと一致する子要素(もしくは子孫要素)を持つ要素を選択し、以下のシンタックスを使用してスタイリングすることができます。
:has(<selectors-list>) {
/* styles */
}
例えば、前述の検索ボックスを見てみましょう。上位のdiv
にクラスsearch-container
が付いており、それが以下の条件に一致する子要素を持っている場合にスタイルを適用します。
- 子要素に
clear-btn
というクラス名があり、 - 子要素がフォーカスモードである
このタスクを達成するために:has()
疑似クラスを次のように使用します。
.search-container:has(.clear-btn:focus) {
outline: 2px solid rgba(66, 153, 225, 0.5);
outline-offset: 5px;
}
それだけでOKです。これでClear
ボタンにフォーカスしたときにのみ、クラスsearch-container
の上位のdiv
がアウトラインスタイルを変更し、他の要素のスタイルはそのままになることがわかります。
さらに、:has()
疑似クラスに複数のセレクタを渡し、Clear
ボタンとSearch
ボタンの両方のフォーカス状態をターゲットにすることもできます。
.search-container:has(.clear-btn:focus, .search-btn:focus) {
outline: 2px solid rgba(66, 153, 225, 0.5);
outline-offset: 5px;
}
素晴らしいですね。これまでに、子要素のフォーカス状態に基づいて親要素をスタイリングする方法として、:focus-within
とhas()
疑似クラスの使用方法を見てきました。次は、どちらをいつ使用するべきか、という疑問が浮かぶでしょう。
:focus-withinと:has(:focus)を使用するタイミング
子要素がフォーカスされているときに親要素をスタイリングすることは、:focus
や:focus-within
を使った場合と比べても、かなりの視認性を向上させることができます。たとえば、商品カードや記事などの長いコンポーネントリストをキーボードでナビゲートする際に、親要素に追加のスタイルをつけることで、ユーザーがフォーカスされている部分とその容器をすばやく識別し、より理解しやすくなります。
一般的に、:focus-within
疑似クラスを使用するのが上記のタスクを達成する最も簡単な方法です。しかし、:focus-within
は全ての子孫のフォーカス状態にもマッチします。これは、複数の子孫がフォーカスモードにある場合、祖先もそのスタイルを変更することを意味しており、必ずしも望ましい振る舞いとは限りません。
たとえば検索ボックスで、検索入力がフォーカスされたときには検索ボックスのラベルをハイライトしたいだけで、上位のdiv
には余計なスタイルを加えたくないかもしれません。そして2つのボタンがフォーカスされたときは、div
コンテナをハイライトします。:focus-within
だけでは、それらのシナリオを同時に実現することはできません。
このような場合には、特定のコンテナに:has(:focus)
を使用する、または:has()
に明示的なセレクタを加えて:has(#search-box:focus)
のようにすることがより適した解決策になるかもしれません。
ただし、ここにはパフォーマンスの問題が潜んでいます。has()
に渡される相対セレクタが複雑になるほど、CSSエンジンはスタイル計算のために一致する全要素をクエリする必要があります。一般的に、:focus-within()
は:has(:focus)
よりも速いですが、その差が重要でない場合もあります。以下に、リスト内の同じ要素li
に両方の疑似クラスを追加した場合のパフォーマンステストのスクリーンショットを示します。
:focus-within
疑似クラスの場合
:has(:focus)
疑似クラスの場合
上記のテストでパフォーマンス差がそれほど大きくないかもしれませんが、DOM構造が複雑である場合や子要素のフォーカス状態を明確に指定する必要があるか、あるいは:focus-within
のような通常の_"ぜんぶ捕まえる"_で十分かどうか、念頭に置いておく価値はあります。
最後に、:has()
機能性疑似クラスはまだFirefoxではサポートされていないので、:focus-within
を使用するか、代わりにフォールバックを追加することを検討したいでしょう。
要約
この記事では、子要素のフォーカス状態に基づいて親要素をスタイリングするための2つの疑似クラスである:focus-within
と:has()
の使用方法について学びました。また、それらの違いとどちらがもう片方よりも適切な選択肢になり得るかについても見てきました。アクセシビリティへの影響は繊細で、直接的なフォーカススタイルのように明らかではないことがありますが、リストアイテムなどにある対話型コンポーネントを内包する親要素に少し視覚効果を与えることは常に優れた考慮です。キーボードナビゲーションユーザーはそれに感謝するでしょう。
👉 私の新しい本Learning VueでVueについて学びましょう。早期リリースが利用可能です!
👉 時々私とコンタクトを取りたければ、Twitter | Facebook | Threadsでフォローしてください。
この投稿が気に入ったり、役に立ったと思ったら共有してください 👇🏼 😉
こちらの記事はdev.toの良い記事を日本人向けに翻訳しています。
https://dev.to/mayashavin/enhancing-focus-visibility-focus-within-or-hasfocus-5em0