:focus-within 疑似クラスでキーボード操作に対応したドロップダウンメニューを作ってみよう

著書 「できるポケット HTML&CSS全事典」 では紙面の関係で細かく書けなかった具体的な実装例を Blog で補足していくシリーズ。今回は :focus-within 疑似クラスを使用して、CSS のみでキーボード操作に対応したドロップダウンメニューを作ってみます。

本題に入る前にちょっとばかり前置きを。

手前味噌で大変恐縮なのですが、今年 2月にインプレス社より 「できるポケット HTML&CSS全事典」 の改訂版を上梓しました (参考エントリー)。

おかげさまで 7月には重版されるなど、多くの方に手に取っていただけているようで、改めましてご購入いただいた皆様には感謝です。

さて、この 「できるポケット HTML&CSS全事典」 は、HTML、および CSS のポケットリファレンスですので、執筆時点で最新の各仕様を基に、すべての HTML 要素と、なるべく多くのセレクタ、プロパティを網羅した形で掲載することを目指しました。結果、大幅改定でページ数も増えたり大変だったんですけども、一方で各項目に使える紙面というのは限りがあって、特にサンプルソースや実装例みたいなものについては最低限しか掲載できていません。

HTML はともかく、特に CSS に関しては、例えばプロパティの解説だけでなく、実装例を充実させてあげるとリファレンスを読んでいる方にも具体的、かつ実用的なソースコードのイメージがつきやすくてよいと思いますが、各セレクタ、プロパティごとにそんなことをやっていたら 1,000ページくらいのとんでもない厚さの書籍になりそう (現状でも 528ページありますし) なので現実的ではありません。

じゃあ、折角 Blog 書いてるんだし、そういう実装例みたいなのはここで書籍の補足情報みたいに書いていけばいいじゃん、ということである程度定期的に書いていく試みをしてみようかなと。

ということで、前置きが非常に長くなってしまいましたが、今回は 「:focus-within 疑似クラス」 について取り上げてみたいと思います。

:focus-within 疑似クラス

:focus-within 疑似クラスは、フォーカスされている、あるいはフォーカスされた要素を含む要素にマッチするセレクタです。各ブラウザの実装状況としては下記のような感じで、Edge(EdgeHTML版)、IE を除けば主要ブラウザですでに実装済み。Edge は Chromium 版に順次刷新されていきますし、IE はもう気にしないと割り切れば実際の制作現場でも問題なく使用可能な環境は整っているといえそうです。

:focus-within 疑似クラスのブラウザ実装状況

:focus-within 疑似クラスの実装例

ということで、タイトルの通り、ドロップダウンメニューを作ってみようと思います。

まず今回使用する HTML としては下記のような簡単なもの。サンプルとしてわかりやすくするためですのでメニュー階層も 2階層までにしてあります。また、class 属性値などもとりあえず最低限にしていますので、あくまで参考ということで。

<div class="sample-menu">
  <ul>
    <li><a href="./">メニュー01</a>
      <ul>
        <li><a href="./">サブメニュー01-01</a></li>
        <li><a href="./">サブメニュー01-02</a></li>
        <li><a href="./">サブメニュー01-03</a></li>
      </ul>
    </li>
    <li><a href="./">メニュー02</a>
      <ul>
        <li><a href="./">サブメニュー02-01</a></li>
        <li><a href="./">サブメニュー02-02</a></li>
        <li><a href="./">サブメニュー02-03</a></li>
      </ul>
    </li>
  </ul>
</div>

ドロップダウンメニューなので、初期状態では 「メニュー01」、「メニュー02」 の部分だけが表示さていて、ユーザーの操作によってその下の階層にあるサブメニューが展開されるという形を想定してやってみます。

ドロップダウンメニューを実装する際、その方法としては CSS のみで実装する方法もありますし、JavaScript を使用する場合もあると思います。その辺は要件などによって最適なものを選択すればよいと思いますが、今回は 「CSS のみ」 での実装を考えます。

CSS のみで今回のようなドロップダウンメニューを実装しようと思った場合、まず思いつくのは :hover 疑似クラスを使った方法ですね。とりあえず :hover 疑似クラスでドロップダウンメニューが動作するようにまで実装してみましょう。

今回は :focus-within 疑似クラスの使用例を解説することが目的ですので、メニューの見た目や細かい実装は無視してください。とりあえず下記のようなスタイルを適用することで、マウスオーバーでサブメニューが展開するドロップダウンメニューは実装できるかと思います。

.sample-menu {
  margin: 1em;
}

.sample-menu ul {
  margin: 0;
  padding: 0;
}

.sample-menu li {
  list-style: none;
}

.sample-menu a {
  display: inline-block;
  padding: .5em 1em;
  white-space: nowrap;
  color: #222;
  text-decoration: none;
}

.sample-menu a:hover {
  background-color: #f48fb1;
}

.sample-menu > ul {
  display: flex;
  align-items: center;
}

.sample-menu > ul > li {
  position: relative;
  background-color: #80deea;
  margin-right: 1px;
}

.sample-menu > ul > li:last-child {
  margin-right: 0;
}

.sample-menu li ul {
  height: 0;
  width: 0;
  overflow: hidden;
  position: absolute;
}

.sample-menu li:hover ul {
  height: auto;
  width: auto;
  background-color: #4fc3f7;
}

ここまで実装すると下記のような感じになります (もし下記のサンプルがうまく表示されない場合はこちらにサンプルページがあります)。

ただし、この実装だと 1つ大きな問題があって、マウス操作でしかサブメニューが展開されず、キーボード操作に対応することができません

そこで登場するのが :focus-within 疑似クラスになります。

先ほどのサンプルソースに下記のように :focus-within 疑似クラスによる記述を加えてみましょう。

.sample-menu {
  margin: 1em;
}

.sample-menu ul {
  margin: 0;
  padding: 0;
}

.sample-menu li {
  list-style: none;
}

.sample-menu a {
  display: inline-block;
  padding: .5em 1em;
  white-space: nowrap;
  color: #222;
  text-decoration: none;
}

.sample-menu a:hover,
.sample-menu a:focus {
  background-color: #f48fb1;
}

.sample-menu > ul {
  display: flex;
  align-items: center;
}

.sample-menu > ul > li {
  position: relative;
  background-color: #80deea;
  margin-right: 1px;
}

.sample-menu > ul > li:last-child {
  margin-right: 0;
}

.sample-menu li ul {
  height: 0;
  width: 0;
  overflow: hidden;
  position: absolute;
}

.sample-menu li:hover ul,
.sample-menu li:focus-within ul {
  height: auto;
  width: auto;
  background-color: #4fc3f7;
}

書き換えた、というかセレクタの記述を追加したのは下記の 2箇所です。

.sample-menu a:hover,
.sample-menu a:focus {
  background-color: #f48fb1;
}

.sample-menu li:hover ul,
.sample-menu li:focus-within ul {
  height: auto;
  width: auto;
  background-color: #4fc3f7;
}

上側の変更は a要素に対してフォーカスが当たった場合でも :hover と同じスタイルを適用するためですのでとりあえず置いておいて、主題は下側の変更ですね。実際に適用されたドロップダウンメニューが下記です (もし下記のサンプルがうまく表示されない場合はこちらにサンプルページがあります)。

マウスで操作している分には特に変わった点はないように見えると思いますが、キーボード操作 (Tab キー) でフォーカスを移動させてみてください。メニューが展開されると思います。

:focus-within 疑似クラスは、それが指定された要素の子孫要素にフォーカスがある間はセレクタにマッチします。

実際にフォーカスを受け取っているのは、:focus-within 疑似クラスが指定されている li 要素自身ではなく、その子孫要素にあたる a 要素なのですが、li 要素内にフォーカスを持った要素が存在する限りはスタイルが適用され続けますので、:hover 疑似クラスと同等の動作をすることになると。

これで、CSS のみでマウス操作、キーボード操作の両方に対応したドロップダウンメニューが実装できました。

実際には今回のように :hover 疑似クラスを使用して実装した場合、iOS / iPadOS など、:hover 疑似クラスに対応しつつも挙動的に :hover 疑似クラスの解除が他の要素のタッチイベント発生時にしか発生しないような場合などは、ドロップダウンメニューが開きっぱなしになって再読込するまで閉じない、みたいなことになったりと、少し考えないといけないことがありますし、当然、:focus-within 疑似クラスに対応していない環境に対しては、JavaScript での実装など別のアプローチが必要になったりと、色々あるんですが、:focus-within 疑似クラスに関してはこういう使い方を知っておくと便利なケースもあるかと思います。

よろしければ参考まで。


ということで最後に宣伝を。

書籍「できるポケット Web制作必携 HTML&CSS全事典 改訂版 HTML Living Standard & CSS3/4対応」は、今回取り上げた :focus-within 疑似クラスをはじめ、現時点である程度実用性のある CSS セレクタ、プロパティに関しては可能な限り幅広く網羅した HTML / CSS のリファレンス本になっていますので、制作現場で使いやすい、コンパクトなリファレンス本をお求めの方は、ぜひお手にとっていただけると幸いです。

書籍「できるポケット Web制作必携 HTML&CSS全事典 改訂版 HTML Living Standard & CSS3/4対応」

「できるポケット HTML&CSS全事典」 のご購入は全国の書店、もしくは下記をはじめとしたオンラインストアよりどうぞ。

Social Share