mcpField
M
modal-and-overlay-patterns

modal-and-overlay-patterns

github.com/dembrandtデザイン・UI174707 回インストール1 週間前に更新
リポジトリ

modal-and-overlay-patterns について

オーバーレイの選択を支援する、デザイナーやフロントエンド開発者向けの UI 設計スキルです。軽量なポップオーバーとブロッキングダイアログを区別し、共通の操作性とアクセシビリティの指針を示します。

主な機能

  1. 01ブロック度に応じた選択
  2. 02ツールチップとポップオーバーの規則
  3. 03選択メニューとアクションメニューの区別
  4. 04モバイル用ボトムシートの規則
  5. 05モーダルの終了と重ね合わせ
  6. 06メディアビューアーのズーム操作

活用シーン

  1. 01破壊的操作の確認フロー設計
  2. 02モバイルナビゲーションパネルの選定
  3. 03製品内のオーバーレイ様式の統一

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/dembrandt/dembrandt-skills --skill modal-and-overlay-patterns

インストール後、Claude Code で/modal-and-overlay-patternsを入力して読み込まれたか確認します。

詳細

モーダルとオーバーレイのパターン

オーバーレイはメインコンテンツレイヤーの上に表示されます。軽量なポップオーバー(操作を妨げず、トリガーに紐づくもの)から、完全に操作をブロックするモーダル(アプリが続行する前にユーザーの応答を必要とするもの)まで、さまざまな種類があります。タスクに適したオーバーレイの種類を選ぶことで、不要な割り込みを防ぎ、ユーザーが現在の状況を把握しやすくなります。


オーバーレイの階層

タスクを満たす最も軽量な種類を選んでください。より重いオーバーレイほど、認知的負荷が高くなります。

種類背景をブロックするトリガーに紐づく一般的なコンテンツ閉じ方
ツールチップいいえはい説明文1~2行カーソルを外す/フォーカスを外す
ポップオーバーいいえはいフォームフィールド、ピッカー、小さなリストなどの短い対話型コンテンツ外側をクリック、Escape、明示的に閉じる
ドロップダウン/メニューいいえはいアクションまたは選択肢のリスト外側をクリック、Escape、選択
ボトムシート(モバイル)部分的(暗転)いいえ小さな画面でのアクションまたはコンテンツ下にスワイプ、スクラムをタップ、Escape
ドロワー/サイドパネル部分的(暗転)いいえ副次的な編集、詳細ビュー、長いフォームEscape、明示的に閉じる。スクラムのクリックも可能
ダイアログ/モーダルはい(スクラムで全面)いいえ操作をブロックするタスク:アクションの確認、必須フォームへの入力Escape(破壊的でない場合のみ)、明示的なボタン
全画面オーバーレイはい(完全にブロック)いいえ没入型タスク:メディアビューアー、複雑な設定明示的に閉じる場合のみ

判断基準: オーバーレイを開いたままアプリのほかの部分を使い続けられる場合は、ブロックしない種類(ドロワー、ポップオーバー)を使用してください。アプリがユーザーの応答を待つ必要がある場合は、モーダルを使用してください。


あらゆる種類に共通する1つのシェル

ツールチップ、ポップオーバー、ドロワー、ボトムシート、ダイアログは、5つの別々のコンポーネントではなく、1つのファミリーです。共通のシェルを使います。同じ隆起したサーフェス、1つの細い境界線、1つの角丸、そしてモーダル性に応じて強くなるシャドウです。ヘッダーとフッターを備えるものでは、内部の構成も共通です。対象を示すタイトル、ヘッダー下の細い線、そしてどこでも統一されたフッターの配置順です。種類によって変わるのはサイズ、配置(矢印付きまたは中央揃え)、ブロックの有無だけです。

オーバーレイごとに個別にスタイルを設定すると、ユーザーは同じメッセージの異なる方言に出会うことになります。角丸、ボタンの順序、確認ラベルがそれぞれ異なります。そのたびに再学習のための時間がかかり、破壊的な操作の確認を読み違えやすくなります。シェルを1つにすれば、コードのプリミティブとデザインツールのファミリーも1つになり、修正をすべての箇所に反映できます。

追加する前に監査してください。 プロダクト内のすべてのフローティングサーフェスと、それぞれが使用するトークンを一覧にしてください。シェルを1つのプリミティブとして切り出し、その上に各オーバーレイの種類を構築します。よくある問題は、確認用ポップオーバーとダイアログでフッターの順序が異なることや、ほかのUIでは使われなくなった重い罫線がダイアログのヘッダーに残っていることです。


ツールチップ

ツールチップは、ホバーまたはキーボードフォーカス時に表示され、トリガーのフォーカスが外れると消えます。純粋に情報を提示するもので、内部に対話型の要素は含めません。

  • コンテンツ:短い文、ラベル、またはキーボードショートカットを1つ。ツールチップ内にリンクやボタンを置かないでください。
  • 表示遅延:ホバー時は300~400ms、キーボードフォーカス時は遅延なし。
  • 位置:トリガーの上を優先し、ビューポートの端が近い場合は自動的に反転させます。
  • ARIA:要素に role="tooltip" を指定し、トリガーの aria-describedby でツールチップのIDを参照します。

ポップオーバー

ポップオーバーはトリガーに紐づきますが、カラー選択ツール、日付範囲セレクター、小さなフォーム、フィルターのリストなど、対話型のコンテンツを含みます。ツールチップとは異なり、ユーザーが操作している間は開いたままになります。

  • 最大幅:280~360px。コンテンツがこれより大きい場合は、ドロワーを使用してください。
  • 位置:トリガーに紐づけ、ビューポート内に収まるよう自動的に反転させます。
  • 閉じ方:外側をクリック、Escapeキー、またはコンテンツが長い場合は明示的な閉じるボタン。
  • フォーカス:開いたときにポップオーバー内へフォーカスを移動し、閉じたときにトリガーへフォーカスを戻します。
  • ARIA:対話型の場合は role="dialog"、リストの場合は role="listbox" を指定し、トリガーに aria-haspopup を設定します。

ドロップダウンとメニュー

ドロップダウンは、トリガーボタンに紐づいた選択肢や操作の一覧を表示します。インタラクティブなオーバーレイの中で最も軽量なものです。

  • 選択用ドロップダウン(ユーザーが値を1つ選び、その値が保持される)と、アクションメニュー(値として保持されない操作をユーザーが実行する)を区別してください。
  • 幅:トリガーと同じ幅以上にし、最大280pxにします。
  • 項目数が多い場合:項目が8~10個を超える場合は、上部に検索入力欄を追加します。
  • キーボード操作:↑/↓で項目間を移動し、Enterで選択し、Escapeで閉じます。

ボトムシート(モバイル)

小さな画面では、モーダルやポップオーバーの代わりにボトムシートを使います。画面の下端からスライドして現れ、タッチデバイスに自然な操作感をもたらします。

  • のぞき見できる高さ: 最初はシートの一部(ハンドルとタイトル)だけを表示し、ユーザーがドラッグして展開できるようにします。
  • 全画面表示: 内容が長い場合や、ビューポート全体を必要とするフォームに使います。
  • 閉じる方法:下にスワイプする、スクリーンをタップする、またはEscapeキーを押します。
  • モバイルではダイアログを画面中央に配置せず、代わりにボトムシートを使います(中央配置のモーダルは小さくなりすぎ、操作しにくくなります)。
  • ARIA:role="dialog"として扱い、モーダルと同じフォーカス管理を適用します。

ドロワー/サイドパネル

ドロワーは左右どちらかからスライドして現れ、メインコンテンツを部分的に覆います。メインコンテンツを使いながら参照する可能性がある、補助的な編集作業、詳細表示、設定に使います。

  • 右ドロワー: 詳細表示、編集フォーム、フィルター/並べ替えパネル。最も一般的です。
  • 左ドロワー: モバイルでのナビゲーション(ハンバーガーメニューのパターン)。
  • 幅:デスクトップでは320~480px。モバイルでは全幅にします(実質的にボトムシートまたは全画面オーバーレイになります)。
  • スクリーン:ドロワーの背後に半透明の背景(rgba(0,0,0,0.4))を表示し、メインコンテンツを暗くします。
  • 閉じる方法:Escapeキー、明示的な閉じるボタン。スクリーンのクリックによる閉じ方は任意です。未保存のフォームがある場合は避けてください。
  • タスクの進行を妨げる場合(必須の判断など)にドロワーを使わないでください。代わりにモーダルを使います。
  • ARIA:role="dialog"、aria-modal="true"を設定し、aria-labelledbyでドロワーのタイトルを指定します。

モーダル/ダイアログ

モーダルは画面全体をスクリーンで覆い、UI全体を操作できない状態にします。ユーザーの応答がなければアプリを続行できない場合に限って使います。

モーダルを使う場合
  • 破壊的または取り消せない操作を確認する場合
  • 続行前に送信が必要な必須フォーム
  • ユーザーの確認が必要なエラーや警告
モーダルを使わない場合
  • ユーザーが都合のよいときに読める情報を表示する場合 → インライン通知または通知を使います
  • メインコンテンツと並行して行いたいタスクの場合 → ドロワーを使います
  • 項目が多い大きなフォームの場合 → 専用ページまたはドロワーを使います
タブを閉じる場合

beforeunloadではブラウザー独自の汎用ダイアログが表示されるだけです。それ以外の処理は実行されず、ダイアログの表示からアンロードまでの間にカスタムマークアップが描画されることもありません。この動作を利用した「離れる前に評価する」フローや「変更内容を確認する」フローを設計しないでください。アプリ側で制御できる操作(閉じるボタン、ルート変更、新しい検索)を対象にし、タブを閉じる操作は回避手段として扱います。

構成
┌──────────────────────────────────┐
│  Title                      [✕]  │  ← Header: title + close button
├──────────────────────────────────┤
│                                  │
│  Body content                    │  ← Content: scrolls if needed
│  (description, form, media)      │
│                                  │
├──────────────────────────────────┤
│               [Cancel]  [Confirm]│  ← Footer: actions, right-aligned
└──────────────────────────────────┘
サイズ
サイズ幅用途
小360px短い確認、単一フィールドのプロンプト
中480px標準的なダイアログ、短いフォーム
大640px複数フィールドのフォーム、情報量の多いコンテンツ
全画面100% viewport没入感のあるタスク。使用は控えめに

モーダルの高さを超えるコンテンツは、本文領域内のみでスクロールするようにします。ヘッダーとフッターは常に表示されたままにします。

モバイルファーストのダイアログには、独自のデスクトップレイアウトを用意します。 大画面の中央に配置されたスマートフォン幅のカードは、窮屈で未完成に見えます。選択肢は狭い1列に折り返され、項目同士の間隔も十分ではありません。モバイルでは元のレイアウトを維持しながら、デスクトップのブレークポイントには専用の幅、列数、間隔を設定します。

選択肢ごとにサブ選択肢が2つか3つの場合は、インラインで表示します。 ピッカーの各選択肢に第2階層の選択肢が2つほどしかない場合は、常に表示されるリンクとして選択肢の下に配置します。アコーディオンや2つ目のダイアログではクリックが必要になり、利用可能な選択肢が見えにくくなります。また、第2の条件を別のセレクターで選ぶ形式では、存在しない組み合わせを選べてしまいます。

閉じる動作
トリガー破壊的でない操作で許可破壊的な操作で許可
Escape キーはいいいえ — 明示的なキャンセルが必要
スクリムの外側をクリックはい(任意)いいえ — 誤って閉じやすいため
閉じるボタン(✕)はいはい
キャンセルボタンはいはい

破壊的な操作や取り消せない操作では、Escape キーと外側のクリックによる閉じる動作を無効にします。ユーザーはキャンセルまたは確認を明示的に押す必要があります。

モーダルの重ね合わせ

モーダルの中から別のモーダルを開くのは避けてください。これは情報設計に問題がある兆候です。タスクが複雑すぎて、1つのダイアログでは扱いきれない可能性があります。

2つ目のオーバーレイが避けられない場合は、別の全画面モーダルではなく、モーダル内にアンカーされたポップオーバーを使います。ブロックするスクリムオーバーレイを2つ重ねることは避けてください。

ライトボックス/メディアビューアー

ライトボックスには、目に見えるタイトルや本文がない場合がありますが、閉じる、ページ移動、ズームなどの操作コントロールがあるため、アクセシブルな名前を持つダイアログでなければなりません。スクリーン、閉じる操作、フォーカス、テキストによる代替に関するルールは引き続き適用されます。ズームとページ移動が操作のすべてであり、フォームと同じくらい丁寧に設計する必要があります。

クリックした項目を開きます。 各サムネイルをクリックすると、最初の画像ではなく、そのサムネイル自身のインデックスでビューアーが開きます。Escape キーで閉じます。開いたときは閉じるボタンにフォーカスを移し、閉じたときはサムネイルにフォーカスを戻します。

画面幅ではなく、ポインターの種類に応じて入力モデルを選びます。 実行時に matchMedia('(pointer: fine)') を使ってデフォルトを選びます。タッチスクリーンを備えたノートパソコンも、トラックパッドを備えたタブレットもあるためです。これはプライマリポインターしか判定しないため、両方のモデルを利用可能にし、それぞれのイベントの pointerType で判断してください。

  • ファインポインター:クリックした位置を中心にズームを切り替えます。パンには、ポインターを動かす方法(すばやく操作できますが、閉じるボタンに手を伸ばすたびに画像が動きます)と、ドラッグする方法(より落ち着いて操作できますが、覚えるジェスチャーがもうひとつ増えます)があります。どちらかを選び、製品全体で統一してください。
  • コースポインター:ピンチでズームし、ダブルタップで切り替え、ドラッグでパンします。これらはユーザーがプラットフォームで慣れ親しんでいる操作です。

ジェスチャーだけでは不十分です。 目に見える +/− ボタンを用意すると、ズームを見つけやすく、キーボードからも操作できるようになります。1×では − を、最大倍率では + を無効にしてください。そうすることで、ボタンが現在の状態も伝えます。+/− キーや矢印キーでパンできるようにするのも、同じルールに沿った適切な拡張です。

2 段階のズーム。 クリックまたはダブルタップすると、ユーザーが触れた位置を中心に、固定の詳細倍率に切り替わります。ボタンを使うと、さらに高い最大倍率まで段階的に拡大できます。倍率は素材に応じて設定します。たとえば、商品写真なら 2× と 4×、地図、スキャン画像、細かい図表ならさらに高い倍率が必要です。一度に最大倍率まで拡大すると、ユーザーはどこを見ているのかわからず、ぼやけたピクセルだけが表示されてしまいます。

カーソルで次の操作を示します。 1×ではズームインカーソル、拡大中はズームアウトカーソル、ビューアーを開くサムネイルには拡大カーソルを表示します。ズームの実装が拡大中の画像に独自の cursor: move を設定する場合は上書きしてください。そうしないと、操作中にヒントが変わってしまいます。

ドラッグとクリックを区別します。 押してから離すまでに数ピクセル(約 5 px)以上動いた場合はパンとみなし、離したときにズームを切り替えてはいけません。そうしないと、パンするたびに画像が 1×に戻ってしまいます。

ズームは画像ごとに適用します。 次の画像に移動するときは 1×にリセットし、画像が 1×に戻ったらスワイプで再びカルーセルを操作できるようにします。

オーバーレイ内のツールの位置を固定します。 閉じるボタンは右上、ページャーは下部中央、ズームコントロールは右下に配置します。ユーザーは一度覚えた配置を頼りにするため、画面ごとに動かすと学習が無駄になります。

状態を伝えます。 すべてのアイコンボタンに aria-label を付けます。それに加えて、アクセシビリティの基本として、ほかのダイアログと同様に現在の倍率をスクリーンリーダーに伝えます。ボタンのラベル(「拡大、現在 2×」など)に含めるか、丁寧な通知を行うライブリージョンを使ってください。また、ズームのトランジションでは prefers-reduced-motion を尊重します。[[wcag-accessibility]] を参照してください。


フォーカス管理

すべてのオーバーレイで、フォーカスを適切に管理する必要があります。フォーカス管理の不備は、アクセシビリティに関する最もよくある問題のひとつです。

開いたとき
  1. オーバーレイ内の最初のインタラクティブ要素(通常は最初の入力欄、確認ダイアログでは確定ボタン)にフォーカスを移します。
  2. フォーカスをトラップします。Tab と Shift+Tab でオーバーレイ内だけを循環し、スクリーンの背後にあるコンテンツへフォーカスが移動しないようにします。
閉じたとき

オーバーレイを開くきっかけとなった要素にフォーカスを戻します。トリガーが存在しなくなっている場合(その要素が削除された場合)は、近くの論理的な要素にフォーカスを移します。

実装上の注意

フォーカストラップライブラリを使うか、最新のブラウザーでネイティブにフォーカストラップを処理する <dialog> 要素を使ってください。手動でフォーカストラップを実装すると、エラーが起きやすくなります。


モーダルとドロワーの ARIA

<div
  role="dialog"
  aria-modal="true"
  aria-labelledby="modal-title"
  aria-describedby="modal-description"
>
  <h2 id="modal-title">Delete project?</h2>
  <p id="modal-description">
    This will permanently delete "Apollo" and all its data. This cannot be undone.
  </p>
  <button>Cancel</button>
  <button>Delete</button>
</div>
  • コンテナーに role="dialog" を設定します
  • aria-modal="true" は、スクリーンリーダーにオーバーレイの背後にあるコンテンツを無視するよう伝えます
  • aria-labelledby には、ダイアログのタイトルの id を指定します
  • aria-describedby には、説明の id を指定します(任意ですが、確認ダイアログでは役立ちます)
  • スクリムの背景には aria-hidden="true" を設定します。スクリーンリーダーで読み上げられてはいけません

破壊的操作の確認パターン

破壊的な操作の確認ダイアログでは、対象の項目とその結果を明示する必要があります。「本当によろしいですか?」のような一般的なダイアログでは、十分な情報が伝わりません。

推奨:

「Apollo Project」を削除しますか? すべてのタスク、ファイル、履歴が完全に削除されます。この操作は元に戻せません。 [キャンセル] [プロジェクトを削除]

非推奨:

この操作を実行してもよろしいですか? [いいえ] [はい]

  • 主要な破壊的操作ボタンには --color-primary ではなく、--color-error / --color-danger を使用します。
  • 破壊的操作ボタンには、「プロジェクトを削除」「メンバーを削除」「注文をキャンセル」のように、内容が明確なラベルを付けます。「OK」や「確認」だけにしてはいけません。
  • キャンセルは常に左側(または副次的な位置)、破壊的操作は右側に配置します。

密着セル上のツールチップ

密着したグループ内のセルに表示するツールチップは、グループ全体を避けて開きます。グリッド、ツリーマップ、セグメントマークなどでセルが密着している場合、各セルのツールチップはグループ全体の上または横に配置し、隣接するセルに重ねてはいけません。下段のセルに固定されたツールチップが、その読み手が次に指そうとしている行を隠してしまうためです。ツールチップがポインターを無視するようにして、隣のセルへ移動したときに次のツールチップが開くようにします。


レビューチェックリスト

  • タスクに適したオーバーレイの種類(ツールチップ/ポップオーバー/メニュー/ボトムシート/ドロワー/モーダル)が選ばれていますか?
  • ドロワーやインラインパターンで十分な場合に、モーダルを避けていますか?
  • モーダル/ドロワーに、視認できるタイトル、本文、明確なアクションボタンがありますか?
  • オーバーフローしたコンテンツは本文内でスクロールし、ヘッダーとフッターは固定されていますか?
  • 破壊的操作では Escape が無効になっていますか(外側をクリックした場合も同様ですか)?
  • 開いたときにフォーカスがオーバーレイ内へ移動し、閉じたときにトリガーへ戻りますか?
  • 開いている間、フォーカスはオーバーレイ内に閉じ込められていますか?
  • role="dialog"、aria-modal="true"、aria-labelledby は正しく設定されていますか?
  • 破壊的操作の確認ダイアログで、対象の項目とその結果が明示されていますか?
  • 破壊的操作ボタンには、「OK」や「確認」ではなく、明確なラベルが付いていますか?
  • モーダルの多重表示を避けていますか?
  • beforeunload を中心にしたカスタム UI を設計していませんか?
  • モバイルでは、モーダルをボトムシートに置き換えていますか?
  • ライトボックス:クリック/パンの操作モデルはポインターの種類に応じていますか。また、ジェスチャーの横に +/− ボタンとキーが配置されていますか?
  • ライトボックス:カーソルにはズームイン/ズームアウトが表示され、パン操作の終了時にズームが切り替わることはありませんか?
  • ライトボックス:ページ切り替え時にズームがリセットされ、現在の倍率がスクリーンリーダーに公開されていますか?
  • 密着セル上のツールチップはグループの外側に開き、ポインターが隣のセルへ移動できるようになっていますか?
  • すべてのオーバーレイで同じシェル(サーフェス、境界線、角丸、影のスケール)とフッターの順序が統一されていますか?

コメント