dialogタグで面倒が減る! 見直したいハンバーガーメニューの作り方

Xへポスト
はてなブックマークへ投稿
共有
URLをコピー

ハンバーガーメニューは、スマートフォン向けウェブサイトでよく使われるナビゲーションUIです。ヘッダーの[≡]ボタンを押すと、リンクの一覧が、現在のページの上に重なって表示されます。

横三本線のボタンが、パン(バンズ)とお肉(パティ)を挟む🍔のように見えることから、慣習的に「ハンバーガーメニュー」と呼ばれています。

新旧さまざまな作り方がありますが、今はHTMLの<dialog>タグで作るのがオススメです! 2020年代以降に登場した新しめのCSSと組み合わせることで、スマートに作れます。

古い作り方を避けたい理由

ハンバーガーメニューは2000年代後半のスマートフォンが出始めた頃よりウェブサイトで流行ってきました。旧式の作り方にどんな課題があったのかを振り返りましょう。もしかしたら「知らずに使っていた😨」といったケースもあるかもしれません。

はりぼてのdivタグ

ハンバーガーメニューの作例では、<div>タグにクラスを付け外しして表示を切り替えるコードを昔からよく見かけます。たとえば、表示状態をクラス名だけで切り替える作り方です。

<!-- 表示状態はクラス名で管理する -->
<button class="menu-button">Menu</button>
<div class="menu is-open">
  <a href="#">Work</a>
</div>

<div>タグで作るデメリットのひとつに、メニューをひらいたままTabキーを押すと、背面のリンクやフォームへフォーカスが移動できることが挙げられます。

作り手は操作できないつもりだったのに、ユーザーが裏技的に操作できてしまい、思わぬトラブルにつながります。

チェックボックスを使うハック

<input>タグのチェックボックスと<label>タグで表示を切り替える方法もあります。JavaScriptなしで動かせる点が利点です。

たとえば、チェック状態をメニューの表示状態として使います。

<!-- チェック状態でメニューを切り替える -->
<input id="menu-toggle" type="checkbox" />
<label for="menu-toggle">Menu</label>
<nav class="menu">...</nav>

とはいえ、ナビゲーションをひらく・とじる方法としてはオススメしません。チェックボックスはフォーム部品なので、支援技術には「メニューをひらくボタン」ではなく「チェックボックス」として伝わります

dialogタグの利点

そこで役立つのが<dialog>タグです。<dialog>タグにはモーダルUIを作るための標準機能があります。

利点

  • showModal()でトップレイヤーに表示できるため、z-indexの調整が不要
  • ハンバーガーメニューをひらいている間、Tabキーで背面へフォーカスが移動しない
  • Escapeキーでとじる標準動作を利用できる
  • 背景の暗い座布団レイヤーを::backdrop疑似要素で指定できる
  • スクリーンリーダーにもダイアログとして伝わる
  • 短いコードで実装できる

▼フォーカストラップが効いていて、下層にフォーカスが移動せず安心

フルスクリーンのメニューやドロワーは、現在のページに重なるレイヤーのため、モーダルUIとして<dialog>タグが活用できます。<dialog>タグという名称から、アラートダイアログのようなUIを想定されがちですが、幅広くモーダルUIにも<dialog>タグが使えると覚えておくと良いでしょう。

<dialog>タグの基本やブラウザごとの挙動は、記事『モーダルUIをシンプルにできる! 進化を続けるHTMLのdialog要素』で詳しく紹介しています。

dialogタグを使った基本形

一般的なハンバーガーメニューの作例をみていきましょう。次のデモの右上の「≡」ボタンで操作できます。

HTMLは次の構成にします。

  • ヘッダー側のひらくボタン
  • <dialog>タグ内のとじるボタン
  • <dialog>タグ内のナビゲーション

基本形のHTMLは、ひらくボタンと<dialog>タグを対応させます。

<!-- ひらくボタン -->
<button id="button-open" class="button-icon button-menu"></button>
<dialog id="menu" class="menu-dialog full-menu fullscreen-menu">
  <!-- とじるボタンとナビゲーション -->
  <button id="button-close" class="button-icon button-close"></button>
  <nav class="fullscreen-menu-nav">...</nav>
</dialog>

JavaScriptでは、ひらく処理にshowModal()メソッド、とじる処理にclose()メソッドを使います。

buttonOpen.addEventListener("click", () => {
  // モーダルとしてひらく
  menu.showModal();
});
buttonClose.addEventListener("click", () => {
  // メニューをとじる
  menu.close();
});

他にも、背景クリックとメニュー内リンクのクリックでも、ハンバーガーメニューをとじられるようにします。詳しくは以下のJavaScriptコードをご確認ください。

フルスクリーン型のCSS

<dialog>タグをハンバーガーメニューとして利用するためには、いくつかスタイルシートで調整します。共通CSSをご覧ください。

  • <dialog>タグのブラウザ標準スタイルを上書き
  • 固定配置、余白、背景色
  • ::backdrop疑似要素
  • 背景スクロールの停止

背景スクロールの停止は、共通CSSで指定しています。

body:has(dialog[open]) {
  /* 背景ページのスクロールを止める */
  overflow: hidden;
}

モーション

モーダルの開閉にモーションを加えると、画面転換のインパクトを和らげ、利用者の視線誘導に役立ちます。

モーダルの開閉モーションは、旧来の作り方ではJavaScriptの実装を必要とし、複雑で難しいものでした。ここ最近のCSSを使うことで、開閉モーションを作りやすくなりました。ポイントは、CSSの@starting-styleallow-discreteアラウ・ディスクリートです。

@starting-styleはCSSトランジションの開始状態を指定できるアットルールです。<dialog>タグは初期状態(非表示)→表示→閉じた状態(非表示)と遷移しますが、はじめの初期状態(非表示)を作るのに@starting-styleが役立ちます。画期的な機能なので覚えておきましょう。

開始時のopacitytranslateは、@starting-styleにまとめます。

.full-menu[open] {
  opacity: 1;
  translate: 0 0;
  @starting-style {
    /* ひらき始めは少し上に置く */
    opacity: 0;
    translate: 0 -24px;
  }
}

@starting-styleの考え方は動画でも解説しています。詳しくはYouTubeの『starting-styleの解説』をご覧ください。

もう1つの、transition-behavior: allow-discreteは別の概念ですが、モーションの実装には重要な役割を持ちます。opacitytranslateは数値が連続的に変化します(たとえば、0.01.0は連続している値です)。たいして、displayは、blockinlinenoneのように中間の値がないプロパティーです。このような値を「離散値りさんち」といいます。「離散」は英語で「Discrete」です。

離散的なdisplayプロパティの切り替えはCSSトランジションと相性が悪いものでしたが、transition-behavior: allow-discreteを使えば、モーションが完了するまで切り替わりを待ってくれます。以下のようにCSSを記載します。

.menu-dialog {
  transition:
    /* 離散値の切り替えも待たせる */
    display 1s allow-discrete,
    overlay 1s allow-discrete;
}

なお、モーションが苦手な人にむけては、メディアクエリーのprefers-reduced-motionも指定できます。詳しくは記事『CSSでもアクセシビリティに配慮しよう! モーション軽減・文字サイズ変更・ダークモードの実装方法 - ICS MEDIA』で解説しています。

モーション中の連打防止

忘れてはいけないのは、モーダルの開閉モーション中の連打防止です。開閉中にモーダルの中のボタンを触れてしまい、不具合が起きる……という事象はフロントエンド開発者なら一度は経験したことがあるでしょう。

とじている間のクリック抑制のため、次のふたつのスタイルを指定しています。

  • pointer-events: none:ポインターデバイスの操作のみブロック
  • interactivity: inert:ポインターとキーボード操作も含めてブロック。HTMLのinert属性のCSS版

サンプルでは、両方を同じ場所で指定しています。

.menu-dialog {
  /* モーション中の操作を止める */
  interactivity: inert;
  pointer-events: none;
}

本来はinteractivity: inertのみでよいのですが、未対応ブラウザがあるためpointer-events: noneも保険のために指定しています。

スクロール防止

メニューをひらいている間は、ページ本体のスクロールを止めます。ページ本体が背面で動くと、メニューをとじたあとに表示位置が変わってしまうためです。

メニュー項目が多い場合は、メニュー内だけをスクロールさせます。フルスクリーン型ではメニュー全体、ドロワー型ではパネル内など、レイアウトに合わせてスクロール領域をつくりましょう。

スクロール制御の考え方は、記事『overscroll-behaviorがお手軽! モーダルUI等のスクロール連鎖を防ぐ待望のCSS』で詳しく紹介しています。

ドロワー型

ドロワーというのは画面端から出現するメニューUIのことです。「ドロワーナビ」「スライドナビ」とかさまざまな呼び方がありますが、これもハンバーガーメニューの一種といえます。ドロワー型も、外側は<dialog>タグです。

<dialog class="menu-dialog side-menu" id="menu">
  <!-- 右から出るパネル -->
  <div class="side-menu-panel">...</div>
  <!-- 三本線ボタンと同じ位置のとじるボタン -->
  <button class="button-icon button-close" id="button-close"></button>
</dialog>

フルスクリーン型とドロワー型は、同じJavaScriptで動かしています。

メニューの中身

ハンバーガーメニューの中身はサイトのレイアウトとして自由に実装できます。作例のバリエーションとして紹介します。

例:アコーディオン

アコーディオンは<details>タグで作っています。

<details>
  <!-- 見出し部分 -->
  <summary><span class="accordion-title">Residents</span></summary>
  <!-- 展開時に出るリンク -->
  <a href="#">Certificates</a>
  <a href="#">Health services</a>
</details>

例:検索フォーム

dialogタグのデメリット

<dialog>タグでの作り方にもデメリットがあります。よくある「三本線のメニューボタンが、連続的に×ボタンへ変形する」ようなモーションは難しくなります。

デモ:CSSアニメーションで実現! コピペで使えるマイクロインタラクション - ICS MEDIA

<dialog>タグでモーダルとしてひらく場合、ひらくボタンはダイアログの外に残ります。とじるボタンは<dialog>タグの内側に置きます。

ひらくボタンととじるボタンが別々のレイヤーになるため、ひとつのボタンを表示状態に合わせて連続的に変形させる作りとは相性がよくありません。

コラム:JavaScriptの制御は近い将来不要になる?

最新ブラウザでは、<button>タグのcommand / commandfor属性で、JavaScriptでのshowModal()メソッドやclose()メソッド相当の操作ができます。さらに、<dialog>タグにclosedby="any"を指定すると、座布団としての::backdrop疑似要素のクリックでもとじます。そうすれば、コードは大幅に短くできます。

command属性の基本は、記事『モーダルやツールチップで役立つ! HTMLのcommandとinterestfor属性を使って、JSを減らすスマートなUI開発』で紹介しています。

将来的には、次のようなHTMLだけで操作できます。

<!-- HTMLだけでdialogをひらく -->
<button commandfor="menu" command="show-modal"></button>
<dialog id="menu" closedby="any">
  <!-- HTMLだけでdialogをとじる -->
  <button commandfor="menu" command="close"></button>
</dialog>

2026年5月時点では一世代前のiOS 18 Safariがcommand属性などに未対応です。iOS 18が現存する間はJavaScriptで制御するほうが無難でしょう。来年の2027年ごろには、JavaScriptを使わずにハンバーガーメニューを作れる時代がやってくると思います。

対応ブラウザ

<dialog>タグは、Chrome 37(2014年8月)、Edge 79(2020年1月)、Safari 15.4(2022年3月)、Firefox 98(2022年3月)以上で利用できます。そのため、現行すべてのブラウザで<dialog>タグが利用可能と受け取って問題ありません。

参照:<dialog>タグ | Can I use…

表示モーションには、@starting-styletransition-behavior: allow-discrete;interactivityプロパティを使っています。

@starting-styleは、Chrome・Edge 117(2023年9月)、Safari 17.5(2024年5月)、Firefox 129(2024年8月)以上で利用できます。

transition-behaviorは、Chrome・Edge 117(2023年9月)、Safari 17.4(2024年3月)、Firefox 129(2024年8月)以上で利用できます。

interactivityプロパティは、Chrome・Edge 135(2025年4月)以上で利用できます。先述のとおり、フォールバックにpointer-eventsを併用します。

参照:

コラム:popover属性を使うケース

画面全体を覆わない簡易メニューでは、<dialog>タグではなく、popover属性を使う作り方もあります。popover属性は非モーダルのため、背面のリンクやフォームは操作できます。ヘッダー右上から小さな一覧を出す、カテゴリ一覧を一時的に見せる、といった用途に向いています。

小さなメニューなら、popover属性とpopovertarget属性で対応できます。

<!-- ボタンが表示対象を指す -->
<button popovertarget="menu-compact"></button>
<div id="menu-compact" popover>...</div>

UIフレームワークはどうなのか?

UIフレームワークのメニューは、<dialog>タグではなく、<div>タグで作られていることが多いです。しかしご安心を。フレームワーク側の膨大なJavaScriptの制御によって、フォーカストラップなどケアがされています。

参考記事:

本記事では<dialog>タグを推奨してきましたが、すでにライブラリ側で担保しているなら、気にせずに使って問題ありません。

まとめ

ハンバーガーメニューを<dialog>タグで作る方法を紹介しました。いまも生の<div>タグで作られているサイトを見かけたら、Tabキー操作をして、考慮されているかを確認してみるといいでしょう。もし裏側を操作できたら、改善すべきサインです

<dialog>タグを使えば、手間なく、高品質なハンバーガーメニューを作れるので、ぜひご検討ください!

SNSでシェアしよう
シェアいただくと、サイト運営の励みになります!
Xへポスト
はてなブックマークへ投稿
共有
URLをコピー
池田 泰延

ICS代表。筑波大学 非常勤講師。ICS MEDIA編集長。個人実験サイト「ClockMaker Labs」のようなビジュアルプログラミングとUIデザインが得意分野です。

この担当の記事一覧