このページの中身

重ねる

何かを上に重ねるとき、決めることは1つです。背後を遮るか、遮らないか。残りはそこから決まります。

モーダル・ドロワー・ポップオーバー・ツールチップを、背後を遮断するかどうかで分けた図
図を大きく見る
図の中身を文字で読む
  • モーダル 背後を遮断する ドロワー 端から出る ポップオーバー 遮断しない ツールチップ 説明だけ
  • 削除しますか 絞り込み 並び替え
  • 削除する
  • 確認・必須の入力・中断できない処理 モバイルのナビ・フィルター 背後を見ながら操作できる タッチでは出ない
  • フォーカスを中へ移し、Escapeで閉じる 出た方向へ戻ることが分かる 中に操作要素を置いてよい 重要な情報を入れてはいけない
  • どれを使うかは「背後を遮断するか」で決まる
  • 遮断する=完了まで先へ進ませたくないとき。ただし文脈を失わせ、操作を中断するので頻度は最小限にする。
  • 入力項目が多い・内容が長い・URLを共有したいなら、モーダルではなく別ページにする。

ひと目で#

モーダル
背後の操作を遮る。終わるまで先へ進ませない
ダイアログ
対話のための窓。モーダルにも、そうでない形にもできる
ドロワー
端から滑り出す。出てきた方向へ戻ればよいと分かる
ポップオーバー
遮らない。中に操作要素を置ける
ツールチップ
説明だけ。タッチでは出ない
フォーカストラップ
モーダルの中でフォーカスを循環させる仕組み

使い分け#

こういうとき使うもの
元に戻せない操作の確認モーダル
モバイルのナビ、フィルタードロワー
背後を見ながら絞り込みたいポップオーバー
アイコンの名前を補いたいツールチップ。アクセシブルネームも付ける
入力項目が多い。内容が長い。URL を共有したい別のページにする
元に戻せる操作確認をやめ、スナックバーの「取り消す」に置き換える

間違えやすいところ#

モーダルを開いてから閉じるまでのフォーカスの道筋。開く前の位置を記憶し、中で循環させ、閉じたら元のボタンへ戻す
図を大きく見る
図の中身を文字で読む
  • フォーカスの道筋
  • 1 開く前の位置 2 開いている間 3 閉じたあと
  • 削除する 削除する
  • この記事を削除しますか
  • この位置を変数に控える 控えた位置へ戻す
  • キャンセル 削除する
  • Tab は2つの間を回り、背後へ抜けない
  • dialog.showModal() なら 2 の閉じ込めと背後の無効化はブラウザが持つ。自分で書くのは 1 と 3 の2行だけ。

直す例#

架空の案件を1つ例にとります。どこを見て、何をどう直すかを書きます。

記事の管理画面です。「削除」を押すと自作の <div> モーダルが開きますが、Esc では閉じられず、Tab を押し続けると背後の一覧へ抜けます。閉じたあとのフォーカスはページ先頭へ飛び、キーボードで操作していた人は自分がどこにいたかを失います。

自作をやめ、<dialog> と showModal() に置き換えます。フォーカスの閉じ込め、背後の無効化、Esc での閉じは、ブラウザが引き受けます。自分で書くのは、開く前の位置を覚えておき、閉じたら戻す数行だけです。取り消せる削除なら、そもそも確認をやめてスナックバーの「取り消す」に置き換える手もあります。

確認する#

この話が要る場面#

このページが答えになる、現場の困り事です。困り事の側から探すと、同じ場所に着きます。

モーダルとドロワーのどちらにするか

作業を止めてよいならモーダル、元の画面を見ながら操作させたいならドロワー。迷ったらページ遷移。

このコードで動いている見本です

この場面のほかの困り事と、そのまま貼れる部品は部品を選ぶにまとめてある。逆引きには全188件。

開いて確かめる#

この値は作業台で決められます。和文の予約ページに載せて、幅と明暗を替えて確かめてから CSS を持ち帰る: 影

モーダル・小型ポップ・トーストを、この場で実際に開いて確かめます。挙動を見て使うものを選ぶと、フォーカスの扱いまで含めた完成コードが出ます。

モーダル dialog

Esc でも外側のクリックでも閉じる

小型ポップ details

絞り込み

JS なしで開閉。背後は遮らない

トースト

3.5秒で自動的に消える

この記事を削除しますか

削除すると元に戻せません。

モーダルを閉じたときは、開いたボタンへフォーカスを戻します(下のコードは記憶と復帰まで含みます)。

<button type="button" id="open-confirm">記事を削除する</button>

<dialog id="confirm" aria-labelledby="confirm-title">
  <div class="dialog-in">
    <h2 id="confirm-title">この記事を削除しますか</h2>
    <p>削除すると元に戻せません。</p>
    <form method="dialog">
      <button class="button button--ghost" value="cancel">キャンセル</button>
      <button class="button button--danger" value="delete">削除する</button>
    </form>
  </div>
</dialog>

<style>
/* .button の基底と派生(--ghost・--danger)は「出発点のCSS」にある */
dialog { padding: 0; border: 1px solid var(--color-border); }
dialog::backdrop { background: rgb(0 0 0 / 0.4); }
/* 余白は内側の要素に置く。dialog 自体に余白があると、その部分のクリックでも閉じる */
.dialog-in { padding: var(--space-5); display: grid; gap: var(--space-3); }
</style>

<script>
var dialog = document.getElementById("confirm");
var opener = document.getElementById("open-confirm");
var trigger = null;
opener.addEventListener("click", function () {
  trigger = document.activeElement;              // 開く前の位置を記憶する
  dialog.showModal();                            // 焦点の閉じ込めと Esc はブラウザが担う
});
dialog.addEventListener("click", function (event) {
  if (event.target === dialog) dialog.close();   // 外側のクリックで閉じる
});
dialog.addEventListener("close", function () {
  if (trigger) trigger.focus();                  // 開いたボタンへ焦点を戻す
});
</script>

この道具は JavaScript で動きます。無効の場合は、出典の MDN「dialog」に同じ型のコードがあります。

出典#

関連#

この資料は、Webサイトを作るときに引くためのものです。 何のために作ったか。作っているのは AK²Lab。