注目の視認性を高める - 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状態もトグルし、親のスタイルがどのように変化するかを見てみましょう。

DevToolsでfocus-within状態をトグルする

: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-withinhas()疑似クラスの使用方法を見てきました。次は、どちらをいつ使用するべきか、という疑問が浮かぶでしょう。

: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疑似クラスの場合

DevToolsでfocus-within状態をトグルする

  • :has(:focus)疑似クラスの場合

DevToolsでfocus-within状態をトグルする

上記のテストでパフォーマンス差がそれほど大きくないかもしれませんが、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