- 技術 💻
モーダルとは|モードレス・ポップアップとの違いと使いどころを実測で
結論
モーダルとは、閉じるまで元の画面を操作できない「モード」に入る表示の仕組みです。画面の上に重なっていても、後ろを操作できるものはモードレス(ポップアップ・トースト・ポップオーバー)で、モーダルではありません。
この記事でわかること
- モーダル・モードレス・ダイアログ・ポップアップ・トーストの言葉の違い
- dialog の showModal()・show()・div 自作・alert を Chromium で動かし、フォーカス・背景の操作・スクロール・Esc を計測した表
- 使う場面・使わない場面の判断表と、よくある不具合(背景が動く・閉じられない・下に隠れる)の直し方
画面の上に何かが重なる表示には、いくつも呼び名があります。見分ける軸は「後ろの画面を操作できるか」と「相手の操作を待つか」の2つです。
flowchart TD
S[画面の上に重ねたい] --> A{後ろを操作<br>させない?}
A -->|はい| M[モーダル<br>確認・必須入力]
A -->|いいえ| B{返事を待つ?}
B -->|はい| D[モードレス<br>ダイアログ]
B -->|いいえ| T[トースト<br>ポップオーバー]
よく調べられることの答えを先にまとめます。
| 知りたいこと | 答え | 詳しい節 |
|---|---|---|
| モーダルとは | 閉じるまで他の操作ができない表示。語源は mode(様式) | モーダルとは |
| モーダルとモードレスの違い | 後ろの画面を操作できるかどうか | モーダルとモードレスの違い |
| モーダルウィンドウとは | モーダルとして出すウィンドウ・ダイアログの呼び名。Web では同じ意味で使われる | ダイアログ・ポップアップとの違い |
| ポップアップとの違い | ポップアップは「重ねて出す」全般。後ろを操作できるものが多い | ダイアログ・ポップアップとの違い |
| HTML でどう作るか | <dialog> の showModal()。背景の無効化・Esc・フォーカスをブラウザが行う |
4つの作り方を実物で比較 |
| 背景がスクロールする | showModal() でも後ろは動く。overflow:hidden と overscroll-behavior で止める |
背景がスクロールする問題 |
| いつ使うか | 取り消せない操作の確認・続けるのに必須の入力だけ | 使う場面・使わない場面の判断表 |
| スマホで崩れる | 高さが画面を超えると上が切れて閉じられない。max-height と overflow で直す | スマホ幅での崩れ方 |
確認環境:Ubuntu 24.04.5 LTS の上で、Python 3.13.18 の http.server を 127.0.0.1 のポートに起動し、自作のテストページ(長い本文・z-index 9999 の固定ヘッダー・<dialog>・div と z-index の自作モーダル・popover)を置きました。そのページを Chromium 153.0.8010.12(Playwright 1.63.0・Python 版)で開き、PC 幅 1200×800 とスマホ幅 375×667 で、showModal()・show()・自作 div(対策なし/inert と overflow の対策あり)・closedby="any"・popover(auto/manual)・alert/confirm/prompt を開き、フォーカスの移り先、Tab の順、座標でクリックしたときに反応した要素、ホイールでの背景のスクロール量、::backdrop、Esc と外側クリック、支援技術に渡るアクセシビリティツリー(Chrome DevTools Protocol の Accessibility.getFullAXTree)を計測しました。言葉の定義は MDN・WAI-ARIA Authoring Practices Guide・Apple の Human Interface Guidelines で確かめています。図は mermaid 11.16.0 で描画を確かめました(確認日 2026年10月29日)。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「モーダルとは|モードレス・ポップアップとの違いと使いどころを実測で」
モーダルとは(意味と語源)
この節では、モーダルという言葉の意味と、なぜ「モーダル」と呼ぶのかが分かります。
モーダル(modal)とは、「ある操作を終える(または取り消す)まで、他の操作ができない状態」のことです。 語源は mode(様式・状態)で、「モードを持つ」が直訳です。Web やアプリの文脈では、画面の上に重ねて表示され、閉じるまで後ろの画面を操作できないウィンドウやダイアログを指して「モーダル」「モーダルウィンドウ」「モーダルダイアログ」と呼びます。Apple の Human Interface Guidelines は、モダリティを「専用のモードでコンテンツを表示して利用者の注意を集めるデザイン手法で、親ビューを操作できず、明示的な操作をしないと閉じられない」と定義しています。
厳密には、モーダルは「状態」の名前で、ダイアログは「部品」の名前です。モーダルなダイアログもあれば、モーダルでない(モードレスの)ダイアログもあります。ただ、Web の記事や会話では「モーダル」だけでモーダルダイアログを指すことが多く、この記事でもそう使います。
IT 以外の「モーダル」
検索すると、UI 以外の意味も混ざって出てきます。この記事は UI のモーダルの話ですが、取り違えないように並べます。
| 言葉 | 分野 | 意味 |
|---|---|---|
| モーダル(ウィンドウ) | UI | 閉じるまで他の操作ができない表示。この記事の主題 |
| マルチモーダル | AI | 文章・画像・音声など複数の種類(モダリティ)の入力を扱うこと |
| モーダルシフト | 物流 | トラック輸送を鉄道や船に切り替えること。mode(輸送手段)の転換 |
| モーダル・インターチェンジ | 音楽 | 同じ主音の別の旋法(モード)からコードを借りること |
| 法助動詞(modal verb) | 英文法 | can・may・must など、話し手の判断を表す助動詞 |
モーダルとモードレスの違い
この節では、モーダルとモードレスを分ける1つの基準と、それぞれの向き不向きが分かります。
違いは「表示している間、後ろの画面を操作できるか」だけです。 できなければモーダル、できればモードレスです。見た目(中央に出る・暗い背景がある)では決まりません。暗い背景があっても後ろをクリックできれば、それはモードレスです。
モーダルとモードレス
見分ける問い:「出したまま、後ろの画面をクリックしたりスクロールしたりできるか」
| モーダル | モードレス | |
|---|---|---|
| 後ろの画面の操作 | できない | できる |
| 利用者の流れ | 中断させ、返事を求める | 中断させない |
| 向く内容 | 取り消せない操作の確認・続けるのに必須の入力 | 補助的な情報・完了の通知・並行して使う道具 |
| 閉じ方 | ボタン・Esc(設計により外側クリック) | 自動で消える・外側クリック・そのまま置いておける |
| HTML での作り方 | dialog.showModal() |
dialog.show()・popover 属性 |
| 手元で確かめた挙動 | 後ろの要素は Tab でもクリックでも触れない | Tab もクリックも後ろに届く |
モーダルは「利用者の手を止める」仕組みなので、必要なときだけ使います。使いすぎると、確認を読まずに OK を押す癖がつき、本当に止めたいときに止められなくなります。
ダイアログ・ポップアップ・トースト・ポップオーバーとの違い
この節では、似た言葉を「後ろを操作できるか」「返事を待つか」「どう閉じるか」で整理した表が分かります。
言葉の使い方はチームや製品ごとに揺れますが、次の表の軸で分けると迷いません。 「ポップアップ」は「重ねて出す表示」全般を指す広い言葉で、別ウィンドウで開く広告も、ページの中に重ねるお知らせも含みます。
| 言葉 | 後ろの操作 | 返事を待つ | 閉じ方 | 例 |
|---|---|---|---|---|
| モーダル(ダイアログ) | できない | 待つ | ボタン・Esc | 「削除しますか?」 |
| モードレスダイアログ | できる | 待たない | 閉じるボタン | 検索と置換のパネル |
| ポップアップ | ものによる | ものによる | ものによる | 別ウィンドウの広告・クーポンの案内 |
| トースト/スナックバー | できる | 待たない | 数秒で自動 | 「保存しました」 |
| ポップオーバー | できる | 待たない | 外側クリック・Esc | メニュー・日付の選択 |
| ツールチップ | できる | 待たない | 離れると消える | アイコンの説明 |
| ボトムシート・ドロワー | 設計による | 設計による | スワイプ・外側タップ | スマホの共有メニュー |
| ライトボックス | できない | 待たない | 外側クリック・× | 画像の拡大表示 |
| alert/confirm/prompt | できない(JS も止まる) | 待つ | OK・キャンセル | ブラウザ標準の確認 |
HTML には、この表に対応する標準の部品が増えています。モーダルは <dialog> の showModal()、モードレスのダイアログは show()、メニューやツールチップのような「外側を押すと消える」表示は popover 属性です。MDN は、Popover API で作るポップオーバーは常に非モーダルで、モーダルにしたいなら <dialog> を使う、と説明しています。Bottom Sheets のように、デザインシステムによってモーダル版とモードレス版の両方が定義されている部品もあります。
モーダルの種類と使いどころ
この節では、モーダルがよく使われる4つの場面と、それぞれで気をつけることが分かります。
モーダルの役割は「利用者の手を止めてでも、返事や注意をもらう」ことです。 使われる場面は、おおむね次の4つに分かれます。
確認(取り消せない操作)
削除・送信・決済・退会の前に「本当に行うか」を聞く。ボタンは2つまでにし、危険な操作の方を既定にしない。
いちばん多い使い方
必須の入力
続けるのにログインや同意が必要なとき。入力項目は少なく、1画面に収める。長い入力は専用ページにする。
フォーカスを最初の欄へ
処理中(ローディング)
二重クリックを防ぎ、固まったと誤解させない。進み具合か、かかる時間の目安を出す。長い処理は中止の手段を置く。
閉じる手段を忘れない
必ず読ませるお知らせ
規約の変更・重要な障害の案内。初回だけ出し、既読を記録する。毎回出すと読まれなくなる。
頻度を絞る
どの場面でも共通する決まりは、1つのモーダルに1つの目的、見出しとボタンの文言で「何が起きるか」が分かること、必ず閉じられること、モーダルの上にモーダルを重ねないこと、の4つです。入れ子については、デザインシステムによって「重ねない」か「フォーカスの管理ができていれば2段まで」かの差はありますが、「重ねないのが基本」という点は共通しています。
4つの作り方を実物で比較(showModal・show・div・alert)
この節では、同じ「確認を出す」を4通りで作り、Chromium で開いたときに何が起きたかを1つの表で比べた結果が分かります。
「モーダルらしさ」を作っているのは、見た目ではなく、フォーカスの移動・後ろの無効化・Esc・閉じたあとのフォーカスの戻りです。 <dialog> の showModal() はこれらをブラウザが行い、div と z-index で作ると自分で書くことになります。テストページで使った中心の部分です。
<!-- 1. dialog 要素(モーダル) -->
<dialog id="dlg" aria-labelledby="dt">
<h2 id="dt">本当に削除しますか?</h2>
<input id="dInput" placeholder="理由(任意)">
<p><button id="dOk">削除する</button> <button id="dCancel">閉じる</button></p>
</dialog>
<script>
openBtn.onclick = () => dlg.showModal(); // モーダル
// openBtn.onclick = () => dlg.show(); // モードレス
dCancel.onclick = () => dlg.close('cancel');
</script>
自作版は、全画面を覆う要素(position:fixed; inset:0; z-index:1000)の中に箱を置き、クラスの付け外しで表示します。対策なしの版は表示を切り替えるだけ、対策ありの版は次の処理を足しました。
// 2. 自作(対策あり):表示に加えて 4 つの仕事を自分で行う
function openCustom() {
prevFocus = document.activeElement; // (1) 戻り先を覚える
overlay.classList.add('open');
for (const el of document.body.children) // (2) 後ろを inert にする
if (el !== overlay) el.inert = true;
document.body.style.overflow = 'hidden'; // (3) 背景のスクロールを止める
overlay.querySelector('input').focus(); // (4) フォーカスを中へ
}
function closeCustom() {
overlay.classList.remove('open');
for (const el of document.body.children) el.inert = false;
document.body.style.overflow = '';
prevFocus && prevFocus.focus(); // 戻り先へ戻す
}
document.addEventListener('keydown', (e) => { // Esc も自分で
if (e.key === 'Escape' && overlay.classList.contains('open')) closeCustom();
});
開く前に本文の入力欄へフォーカスを置き、開いたあとに Tab を6回押し、固定ヘッダーのボタンと本文のボタンの座標をクリックし、ホイールで 600px 分スクロールし、Esc を押しました。結果です。
| 計測した項目 | dialog showModal() | dialog show() | div(対策なし) | div(inert+overflow) |
|---|---|---|---|---|
| 開いた直後のフォーカス | 中の入力欄(自動) | 中の入力欄(自動) | 開くボタンのまま | 中の入力欄(自分で) |
| Tab を6回押した移り先 | OK→閉じる→入力欄→…(中で循環) | OK→閉じる→ヘッダー→本文(外へ出る) | 本文のボタンを順に(中に入らない) | OK→閉じる→入力欄→…(中で循環) |
後ろの要素に focus() |
できない | できる | できる | できない |
| ヘッダー(z-index 9999)のボタンをクリック | dialog が受けた(押せない) | ボタンが押せた | ボタンが押せた(覆いより上) | 覆いが受けた(押せない) |
| 本文のボタンをクリック | dialog が受けた | ボタンが押せた | 覆いが受けた | 覆いが受けた |
| ホイールで背景が動いた量 | 600px | 600px | 600px | 0px |
| Esc で閉じる | 閉じた(cancel→close) | 閉じない | 閉じない | 閉じた(自分で) |
| 閉じたあとのフォーカス | 開くボタンへ戻った(自動) | 開くボタンへ戻った(自動) | body(迷子) | 開くボタンへ(自分で) |
| 支援技術に渡る要素の数 | 27(後ろが消える) | 79 | 80 | 30 |
::backdrop |
ある(rgba(0,0,0,0.5)) | ない | ない(自分で覆いを描く) | ない(自分で覆いを描く) |
Tab の列の「…」は、中の最後の要素からブラウザ自体の UI へ一度移り、そのあと中の最初の要素へ戻る動きです(後ろのページには移りません)。
この表から、showModal() で足りないのは「背景のスクロール」だけだと分かります。逆に、対策なしの div は「見た目はモーダル、動きはモードレス」で、固定ヘッダーのボタンが押せてしまうなど、モーダルの条件を1つも満たしていません。
1show() は「置いた場所」に出る
show() で開いた dialog の位置は y=2679px で、画面の外だった。モードレスは文書の流れの中(DOM の位置)に出るため、置く場所か CSS で位置を決める。
2aria-modal="true" を書いても後ろは消えない
div 版は role="dialog" と aria-modal="true" を付けていたが、支援技術に渡る要素は 80 個のままだった。属性は「宣言」で、後ろを本当に無効にするのは inert か showModal()。
3処理時間はどちらも 1ms 未満
showModal()→close() を 100 回で 1 回あたり 0.36ms、div の表示切り替えは 0.18ms。速さで選ぶ差は無く、挙動の正しさで選ぶ。
フォーカスの移動・Tab・支援技術から見えるもの(inert と aria-modal)
この節では、モーダルで最も大切な「フォーカスの扱い」と、スクリーンリーダーなどの支援技術に何が渡っているかの計測が分かります。
WAI-ARIA の Authoring Practices Guide は、モーダルダイアログの条件として、開いたらフォーカスがダイアログの中へ移ること、Tab と Shift+Tab がダイアログの中で循環すること、Escape で閉じること、閉じたら開いた要素へフォーカスが戻ること、を挙げています。 これを手元の4通りで確かめたのが前の節の表です。
支援技術に何が渡っているかは、Chrome DevTools Protocol でアクセシビリティツリーを取り出して数えました。
閉じているとき : 79 ノード 本文のボタン1=あり ヘッダーのボタン=あり
showModal() : 27 ノード 本文のボタン1=なし ヘッダーのボタン=なし dialog modal=true
show() : 79 ノード 本文のボタン1=あり ヘッダーのボタン=あり dialog modal=false
div + aria-modal=true : 80 ノード 本文のボタン1=あり ヘッダーのボタン=あり dialog modal=true
div + inert : 30 ノード 本文のボタン1=なし ヘッダーのボタン=なし dialog modal=true
div(aria-modal なし): 80 ノード 本文のボタン1=あり ヘッダーのボタン=あり dialog modal=false
showModal()は、後ろの要素をアクセシビリティツリーから外し、aria-modalを書かなくても modal=true として伝えます。 MDN も、showModal()で開いたダイアログは暗黙にaria-modal="true"になり、show()やopen属性ではaria-modal="false"として扱われる、と説明しています。- div 版で
aria-modal="true"を書いただけでは、後ろの 50 個余りの要素は支援技術に見えたままでした。APG も、aria-modal="true"は「後ろの操作をすべての利用者に対して防いでいる」ときに限って付けるべきだ、としています。見た目だけ覆って属性を付けると、スクリーンリーダーの利用者には「モーダルと言われたのに後ろが読める」矛盾した状態になります。 inert属性を後ろに付けると、Tab でもクリックでも触れなくなり、ツリーからも外れました。座標でヘッダーのボタンをクリックしたとき、対策なしの版ではヘッダーのボタンが反応し、inert ありの版では覆いが受けました。inert な要素はクリックの対象からも外れるためです。
開いたときの最初のフォーカスは、showModal() では中の最初のフォーカスできる要素に移りました。MDN は、最初に操作してほしい要素に autofocus を付けること、無ければ閉じるボタンかダイアログ自体に付けることを勧めています。APG は、削除のような取り消せない操作の確認では、最も破壊的でないボタン(キャンセル側)に置く考え方も示しています。
背景がスクロールする問題(showModal でも起きる)
この節では、モーダルを開いているのに後ろのページが動いてしまう原因と、2つの直し方の計測結果が分かります。
showModal() は後ろの操作を無効にしますが、ホイールやスワイプでのスクロールまでは止めません。 手元では、モーダルを開いた状態でホイールを回すと、後ろのページが 600px 動きました。長い本文の上でモーダルを出すと、閉じたときに見ていた場所が変わっている、という不具合になります。
直し方は2つを組み合わせます。1つ目は、モーダルが開いている間だけ body のスクロールを止めることです。:has() と :open を使うと JS なしで書けます。
/* dialog が開いている間だけ、後ろのスクロールを止める */
body:has(dialog:open) { overflow: hidden; }
対策なし : scrollY 0 → 600(ホイール 600px で後ろが動いた)
body:has(dialog:open){overflow:hidden} : scrollY 0 → 0(止まった)
2つ目は、モーダルの中が長くてスクロールするときの「連鎖」です。中を端までスクロールしてさらにホイールを回すと、後ろのページへスクロールが伝わります(スクロールチェイン)。overscroll-behavior: contain で止まります。中身を 60 段落に増やしてホイールを 12 回回した結果です。
既定 : dialog の scrollTop 1640(端まで)→ 後ろの scrollY 1895(伝わった)
dialog{overscroll-behavior:contain} : dialog の scrollTop 1640(端まで)→ 後ろの scrollY 0(止まった)
dialog { overscroll-behavior: contain; max-height: calc(100dvh - 32px); overflow: auto; }
overflow: hidden を使うと、スクロールバーの幅の分だけページの横幅が変わり、開いた瞬間に背景が横にずれることがあります。手元の Chromium はオーバーレイ型のスクロールバー(幅 0px)だったので起きませんでしたが、Windows のように幅のあるスクロールバーでは scrollbar-gutter: stable を html に付けるとずれを防げます。
閉じ方:Esc・外側クリック・closedby・入れ子
この節では、モーダルをどう閉じるべきか、ブラウザが何を自動で行うか、外側クリックで閉じる設定の実物が分かります。
モーダルは「必ず閉じられる」ことが条件です。 APG は、閉じるための目に見えるボタン(×やキャンセル)をタブ順に含めることを強く勧めています。Esc や外側クリックは補助で、ボタンの代わりにはなりません。
| 閉じ方 | showModal()(既定) | closedby="any" | popover(auto) | popover="manual" |
|---|---|---|---|---|
| Esc | 閉じた | 閉じた | 閉じた | 閉じない |
| 外側クリック | 閉じない(dialog が受ける) | 閉じた | 閉じた(後ろのボタンも押せた) | 閉じない |
| 閉じたあとのフォーカス | 開くボタンへ | 開くボタンへ | 開くボタンへ | (閉じるボタンで)body |
| 開いたときのフォーカス | 中へ移る | 中へ移る | 移らない | 移らない |
closedByの既定値を読むと"closerequest"でした。MDN によると、closedbyはany(外側クリック・Esc・自分の仕組み)、closerequest(Esc と自分の仕組み)、none(自分の仕組みだけ)の3つで、showModal()で開いたときの既定はcloserequestです。外側クリックで閉じたいときは<dialog closedby="any">と書くだけで、クリックの位置を自分で判定する必要がなくなりました。- Esc で閉じたとき、
cancelイベントの後にcloseイベントが発生し、returnValueは空のままでした。入力の途中で Esc を押したときに「破棄してよいか」を聞きたいなら、cancelでpreventDefault()します。 - popover(auto)は、外側をクリックすると閉じると同時に後ろのボタンも押せました(light dismiss)。後ろを触らせたくない用途には向かず、メニューやツールチップのような「どいてほしい表示」に向きます。
入れ子にしたとき
モーダルの上にもう1つ showModal() で開くと、フォーカスは2つ目へ移り、1つ目のボタンは focus() しても受け付けなくなりました(1つ目も inert になる)。Esc を1回押すと2つ目だけが閉じ、1つ目は開いたままで、フォーカスは1つ目の入力欄へ戻りました。ブラウザは入れ子を正しく扱いますが、使う側の負担は増えるので、設計としては避けます。
エラーの原文
show() で開いたあとに showModal():
InvalidStateError: Failed to execute 'showModal' on 'HTMLDialogElement': The dialog is already open as a non-modal dialog, and therefore cannot be opened as a modal dialog.
showModal() を 2 回: エラーなし(開いたまま)
open 属性を直接付けて開くとモードレスになり、後ろの要素に focus() できました。MDN も、open 属性ではなく show()・showModal() を使うよう勧めています。
alert・confirm・promptとの違い(JS が止まる)
この節では、ブラウザ標準の alert()・confirm()・prompt() がモーダルとどう違うのか、計測した結果が分かります。
alert() などは、ページの JavaScript そのものを止める「同期のモーダル」です。 見た目や文言をほとんど変えられず、ブラウザによっては「このページで追加のダイアログを表示しない」が出て以後は出なくなります。手元で、ダイアログに応答するまで 300ms 待たせたときの結果です。
alert('…') が戻るまで : 305ms(応答するまで JS が止まる)
alert 中に setTimeout(…, 10) : alert が戻るまで発火しない
confirm → OK : true
confirm → Esc(キャンセル) : false
prompt → 入力して OK : "入力した値"
prompt → キャンセル : null
| alert/confirm/prompt | dialog showModal() | |
|---|---|---|
| JS の実行 | 止まる(タイマーも止まる) | 止まらない(非同期) |
| 見た目・文言 | ブラウザ任せ。HTML は入らない | 自由(CSS・任意の HTML) |
| 結果の受け取り | 戻り値(true/false・文字列・null) | close イベントと returnValue |
| 自動テスト | ダイアログの処理を別に書く | 普通の要素として操作できる |
| 向く場面 | 開発中の確認・管理画面の簡易な確認 | 利用者に見せる画面全般 |
confirm() の「はい・いいえ」を <dialog> で置き換えるときは、close('ok') のように値を渡し、close イベントで dialog.returnValue を読みます。<form method="dialog"> を使うと、ボタンの value がそのまま returnValue になります。
スマホ幅での崩れ方と直し方(375px で計測)
この節では、PC では問題なかったモーダルがスマホで「上が切れて閉じられない」「横にはみ出す」になる原因と、直した前後の数値が分かります。
原因は、モーダルの高さが画面を超えたときに、中央寄せが箱の上下を画面の外へ押し出すことです。 幅 375×高さ 667 の画面で、中身を 40 段落に増やして開きました。
| 作り方 | 箱の位置と大きさ(x, y, 幅, 高さ) | OK ボタンが画面内か |
|---|---|---|
| div 自作(flex で中央寄せ・高さ制限なし) | 0, -519, 375, 1706 | いいえ(上に切れて届かない) |
div 自作+max-height: calc(100dvh - 32px); overflow: auto |
19, 16, 338, 635 | はい |
| dialog(width: 360px; padding: 20px) | 0, -4, 383, 675 | はい(中はスクロールできる) |
dialog+box-sizing: border-box |
19, 19, 337, 629 | はい |
- 自作版は、箱の上端が y=-519px、つまり画面の 519px 上に出ていました。箱自体はスクロールしない(
overflow: visible)ので、見出しも OK ボタンも触れません。スマホで「閉じられないモーダル」になる典型です。箱に最大の高さとoverflow: autoを付けると、箱の中がスクロールするようになり、ボタンに届きました。 <dialog>は、既定でmax-widthとmax-heightがcalc(100% - 38px)、overflow: autoになっていて、中身が長くても中でスクロールしました。ただし、width: 360pxにpaddingと枠線が足されて、375px の画面で幅 383px にはみ出しました。box-sizing: border-boxを付けると 337px に収まります。- 高さの単位は
vhではなくdvhを使います。スマホのブラウザはアドレスバーの分だけ表示領域が伸び縮みし、100vhは大きい方の高さで固定されるためです。
箱に最大の高さと中のスクロールを付ける
max-height: calc(100dvh - 32px); overflow: auto;。中央寄せの前に、箱が画面に収まる保証を作る。
幅は border-box で数える
box-sizing: border-box; width: min(360px, 100% - 32px);。padding と枠線を含めて画面に収める。
ボタンを下に固定する
長い内容なら、見出しとボタンを position: sticky で上下に固定し、本文だけをスクロールさせる。
スマホ幅で開いて確かめる
DevTools のデバイスモードか実機で、いちばん長い内容のモーダルを開き、ボタンまで届くかを見る。
z-index で下に隠れる・transform の親で崩れる
この節では、自作モーダルが固定ヘッダーの下に隠れたり、画面を覆わなくなったりする原因と、<dialog> が影響を受けない理由が分かります。
showModal() で開いた dialog は「最上位レイヤー(top layer)」に置かれ、ページ内のどんな z-index よりも上に出ます。 div と z-index で作ると、重なり順は z-index とスタッキングコンテキストの規則で決まり、2つの典型的な崩れ方が起きます。
| 状況 | 手元で起きたこと | 直し方 |
|---|---|---|
| 固定ヘッダーが z-index 9999、覆いが 1000 | ヘッダーが覆いの上に出て、ヘッダーのボタンが押せた | 覆いの z-index をヘッダーより上にする。または dialog にする |
覆いの親に transform がある(z-index 999999) |
覆いの大きさが 1168×0px、位置は親の場所。画面を覆わない | 覆いを body 直下へ出す(React の createPortal・Vue の Teleport)。または dialog |
| dialog showModal() | z-index 9999 のヘッダーより上に出て、ヘッダーのボタンは dialog が受けた | (対策不要。top layer) |
2つ目は見落としやすい崩れ方です。position: fixed は普通は画面を基準にしますが、祖先に transform・filter・will-change などがあると、その祖先を基準にします。アニメーションのために親へ transform を付けた途端、覆いが画面を覆わなくなります。z-index を 999999 にしても、親のスタッキングコンテキストの中で比べられるだけで、外のヘッダーには勝てません。スタッキングコンテキストの原因の一覧は、関連記事「CSS z-indexが効かない|スタッキングコンテキスト原因一覧」で扱っています。
フレームワークで作るときは、モーダルの要素を body 直下へ移す仕組みが用意されています。React は関連記事「React createPortalの使い方|モーダルとz-index」、Vue は関連記事「Vue Teleportの使い方|モーダルをbodyへ移動・defer対応」で扱っています。Next.js の App Router では、URL を持つモーダル(一覧から詳細を開いても戻るで閉じられる)を関連記事「Next.js Parallel・Intercepting Routesでモーダル実装」で扱っています。
使う場面・使わない場面の判断表
この節では、「モーダルにするか、別の見せ方にするか」を決める順番と、場面ごとの判断が分かります。
次の順に問うと、モーダルを使いすぎずに済みます。
- 1返事をもらわないと先に進めないか → いいえならモーダルにしない保存完了・軽い案内はトーストやインラインの文言
- 2取り消せない操作か → はいなら確認のモーダル削除・送信・決済。取り消せるなら「元に戻す」付きのトースト
- 3後ろの画面を見ながら作業するか → はいならモードレスか別ページメール本文を見ながらの返信・検索と置換
- 4入力が多い・何段階もあるか → はいなら専用ページスマホ幅で 1 画面に収まらない入力はページにする
- 5モーダルの中から別のモーダルを開くか → はいなら設計を見直す重ねない。1 つにまとめるかページへ
- 6ここまで残ったものだけモーダルにする1 つの目的・2 つまでのボタン・必ず閉じられる
| 場面 | 判断 | 代わりの見せ方 |
|---|---|---|
| 投稿の削除の確認 | モーダル | 取り消せるなら「削除しました・元に戻す」のトースト |
| 保存できました | 使わない | トースト・ボタンの横の文言 |
| ログインが必要 | モーダルか専用ページ | 続きの操作に必須なら可。戻る先を保つ |
| Cookie の同意 | 多くはバナー | 画面下のバナー。全面を覆うと読まれない |
| FAQ の回答を開く | 使わない | アコーディオン。タップのたびに覆うのは煩わしい |
| 画像の拡大 | ライトボックス(モーダル) | 外側クリックと Esc で閉じられるようにする |
| 10 項目の登録フォーム | 専用ページ | スマホで収まらない。途中保存もしやすい |
| メニュー・日付の選択 | ポップオーバー | 外側クリックで消える。後ろは触れてよい |
| 処理中(数秒) | ボタンの無効化+スピナー | 数十秒なら進み具合つきのモーダル |
よくある不具合と直し方
この節では、モーダルで起きやすい不具合を、原因と直し方の対応表で確かめられます。
| 不具合 | 原因 | 直し方 |
|---|---|---|
| 後ろのページがスクロールする | showModal() も自作も、スクロールは止めない | body:has(dialog:open){overflow:hidden}。中が長いなら overscroll-behavior:contain |
| スマホで上が切れて閉じられない | 箱が画面より高く、中央寄せで上下が外に出る | 箱に max-height: calc(100dvh - 32px); overflow:auto |
| 固定ヘッダーの下に隠れる | 覆いの z-index がヘッダーより小さい | z-index を上げる。根本は dialog(top layer)に |
| 覆いが画面全体を覆わない | 親の transform などで fixed の基準が変わった |
body 直下へ(createPortal・Teleport)か dialog |
| Tab で後ろの要素に移る | 後ろが inert になっていない | showModal() か、後ろに inert |
| 閉じたらフォーカスが迷子 | 戻り先を覚えていない | showModal() なら自動。自作は開く前の activeElement を覚えて戻す |
| Esc で閉じない | 自作で keydown を書いていない | showModal() なら自動。自作は keydown で Escape を見る |
| 外側クリックで閉じない | dialog の既定は closerequest | closedby="any" |
| InvalidStateError が出る | show() で開いたまま showModal() を呼んだ | 先に close() するか、開き方を1つに揃える |
| スクリーンリーダーに後ろが読まれる | aria-modal を書いただけで、後ろが無効になっていない | showModal() か inert。属性だけで済ませない |
| 開いた瞬間に背景が横にずれる | overflow:hidden でスクロールバーの幅が消えた | html{scrollbar-gutter:stable} |
| 毎回出て読まれない | 既読を記録していない | 初回だけ出し、表示済みを保存する |
- 1開いたらフォーカスが中へ移るTab を押して中の要素だけを回るか
- 2後ろをクリックしても反応しない固定ヘッダーのボタンも押せないか
- 3Esc と目に見える閉じるボタンの両方で閉じる閉じたら開いたボタンへフォーカスが戻るか
- 4後ろがスクロールしない中を端まで送っても後ろに伝わらないか
- 5375px 幅でボタンまで届くいちばん長い内容で確かめる
- 6見出しが aria-labelledby で結ばれているスクリーンリーダーが「何のダイアログか」を読めるか
コピーして使える <dialog> の完成形(開閉・Esc・外側クリック・スクロール固定・アニメーション・a11y)は、関連記事「HTML dialog要素でモーダル実装|コピペ完成版とa11y対応」で扱っています。この記事は「モーダルとは何か・どれを選ぶか」の入口で、実装の細かい手順はそちらに任せています。
よくある質問
Q. モーダルとは何ですか? A. 閉じるまで他の操作ができない「モード」に入る表示の仕組みです。Web では、画面の上に重なって後ろを操作できなくするウィンドウやダイアログを指します。語源は mode(様式・状態)です。
Q. モーダルとモードレスの違いは何ですか? A. 表示している間に後ろの画面を操作できるかどうかです。できなければモーダル、できればモードレスです。見た目(中央に出る・背景が暗い)では決まりません。
Q. モーダルウィンドウとモーダルダイアログは同じですか? A. Web の文脈ではほぼ同じ意味で使われます。厳密には「モーダル」は状態の名前、「ダイアログ」「ウィンドウ」は部品の名前です。OS のウィンドウとしてのモーダルウィンドウと、Web ページの中に重ねるモーダルダイアログを区別する人もいます。
Q. ポップアップとモーダルの違いは何ですか? A. ポップアップは「重ねて出す表示」全般の広い言葉で、別ウィンドウの広告も含みます。モーダルはその中で「後ろを操作できないもの」です。後ろを操作できるポップアップはモードレスです。
Q. トーストとは何ですか? A. 画面の端に数秒だけ出て自動で消える短い通知です。「保存しました」のような、返事の要らない知らせに使います。後ろは操作できるのでモードレスです。Material Design ではスナックバーと呼びます。
Q. HTML でモーダルを作るにはどうしますか?
A. <dialog> 要素を置き、dialog.showModal() で開きます。後ろの無効化・Esc・フォーカスの移動と戻り・最上位レイヤーへの配置をブラウザが行います。手元の計測では、足りないのは背景のスクロールだけで、body:has(dialog:open){overflow:hidden} を足します。
Q. div と z-index で作ってはいけませんか? A. 作れますが、フォーカスの移動と戻り・後ろの inert 化・Esc・スクロール固定を自分で書く必要があります。手元では 17 行足して同じ挙動になりました。書き忘れると「見た目だけモーダル」になり、固定ヘッダーのボタンが押せる・Tab で後ろへ抜ける、が起きます。
Q. モーダルは SEO に影響しますか? A. モーダルの中身も HTML にあれば検索エンジンは読めます。ただし Google 検索セントラルは、ページ全体を覆うインタースティシャルや、プロモーション用のダイアログを「煩わしいインタースティシャル」として避けるよう求めています。年齢確認のように法的に必要なものは例外ですが、その場合もコンテンツの上に重ねて一部は読める形にし、同意のために別ページへ転送しないことを勧めています。開いた直後に全面を覆うお知らせは出さず、必要なときだけ出します。
Q. alert() を使うのはいけませんか?
A. 開発中の確認には便利ですが、JavaScript が止まり、文言や見た目を変えられず、自動テストでも扱いが別になります。利用者に見せる画面では <dialog> に置き換えます。
Q. モーダルの上にモーダルを開いてもよいですか? A. ブラウザは入れ子を正しく扱い、Esc で上の1つだけが閉じました。ただし利用者の負担が増えるので、設計としては重ねないのが基本です。1つにまとめるか、専用ページにします。
Q. 外側をクリックしたら閉じるべきですか?
A. 内容によります。画像の拡大やメニューは閉じてよく、入力の途中や取り消せない操作の確認は、誤って閉じると困るので閉じない方が安全です。<dialog closedby="any"> で切り替えられます。どちらでも、目に見える閉じるボタンは必ず置きます。
Q. スマホでモーダルが閉じられなくなります。
A. 箱の高さが画面を超え、中央寄せで上下が画面の外に出ているのが典型です。箱に max-height: calc(100dvh - 32px); overflow: auto を付けると、中がスクロールしてボタンに届きます。
まとめ
- モーダルとは、閉じるまで他の操作ができない「モード」に入る表示。後ろを操作できればモードレス(トースト・ポップオーバー・モードレスダイアログ)で、見た目では決まらない。
- HTML では
<dialog>のshowModal()がモーダルの標準。手元の計測で、フォーカスの移動と戻り・Tab の循環・後ろの無効化・Esc・最上位レイヤーへの配置をブラウザが行い、支援技術に渡る要素は 79 個から 27 個に減った。 - 足りないのは背景のスクロールだけ。
body:has(dialog:open){overflow:hidden}とoverscroll-behavior:containで止まった(600px → 0px)。 - div と z-index の自作は、inert・フォーカス・Esc・スクロール固定を自分で書かないと「見た目だけモーダル」になる。
aria-modal="true"を書いても後ろは支援技術に見えたまま(80 個)だった。 - 使うのは「取り消せない操作の確認」と「続けるのに必須の入力」だけ。1つの目的・2つまでのボタン・必ず閉じられる・重ねない。スマホ幅では
max-heightとoverflow:autoで上が切れないようにする。
参考・出典
確認日はいずれも 2026年10月29日です。
- MDN「<dialog>: ダイアログ要素」:https://developer.mozilla.org/ja/docs/Web/HTML/Reference/Elements/dialog
- MDN「HTMLDialogElement: showModal() メソッド」:https://developer.mozilla.org/ja/docs/Web/API/HTMLDialogElement/showModal
- MDN「inert」:https://developer.mozilla.org/ja/docs/Web/HTML/Reference/Global_attributes/inert
- MDN「ポップオーバー API」:https://developer.mozilla.org/ja/docs/Web/API/Popover_API
- MDN「ポップオーバー API の使用」:https://developer.mozilla.org/ja/docs/Web/API/Popover_API/Using
- MDN「最上位レイヤー(Top layer)」:https://developer.mozilla.org/ja/docs/Glossary/Top_layer
- W3C WAI「Dialog (Modal) Pattern」ARIA Authoring Practices Guide:https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/
- Apple「モダリティ」Human Interface Guidelines:https://developer.apple.com/jp/design/human-interface-guidelines/modality
- Google 検索セントラル「煩わしいインタースティシャルとダイアログを避ける」:https://developers.google.com/search/docs/appearance/avoid-intrusive-interstitials
- Playwright「Dialogs」:https://playwright.dev/python/docs/dialogs
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「モーダルとは|モードレス・ポップアップとの違いと使いどころを実測で」
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。