モーダルウィンドウとは

背景を暗くして、画面の上に重ねて表示する小さなウィンドウ。閉じるまで背後を操作できない。

概要

ページの上に重ねて表示され、背景を暗く(暗転)して操作をそのウィンドウに集中させる部品です。 確認メッセージや簡単な入力フォームなど、「今すぐ答えてほしい」内容を表示するのに使われます。 閉じるまでは背景のページを操作できない(モーダル=modal は「様式・様態を限定する」という意味の形容詞)のが特徴です。

別名

表記ゆれ:モーダル、モーダルウィンドウ、モーダルダイアログ、モーダル表示、modal、modal window

単に「モーダル」と呼ばれることが最も多く、「ダイアログ」とほぼ同じ意味で使われることもあります。

AI への伝え方

「モーダルウィンドウを実装して」「モーダルで確認画面を出して」で通じます。 背景クリックで閉じてほしい・閉じてほしくない(誤操作防止)は明示すると仕様がぶれません。

似たパーツとの違い

「ダイアログ」はモーダルとほぼ同義で使われますが、モーダルは特に背景の暗転と操作ブロックを伴う実装を指すことが多い言葉です。 画像を拡大表示するだけのものは「ライトボックス」、下からスライドして出てくるものは「ボトムシート」、 背景を暗転させず軽い情報を出すだけのものは「ポップアップ」と呼び分けます。

デモ

コード

HTML
<div class="modal-demo">
  <button class="modal-demo__open" type="button" data-modal-open="sample-modal">
    モーダルを開く
  </button>

  <div class="modal-demo__overlay" id="sample-modal" hidden>
    <div class="modal-demo__window" role="dialog" aria-modal="true" aria-labelledby="sample-modal-title">
      <div class="modal-demo__header">
        <h2 id="sample-modal-title">確認</h2>
        <button class="modal-demo__close" type="button" data-modal-close aria-label="閉じる">×</button>
      </div>
      <div class="modal-demo__body">
        <p>この内容でよろしいですか?背景をクリックしても閉じられます。</p>
      </div>
      <div class="modal-demo__footer">
        <button class="modal-demo__cancel" type="button" data-modal-close>キャンセル</button>
        <button class="modal-demo__confirm" type="button" data-modal-close>OK</button>
      </div>
    </div>
  </div>
</div>
CSS
.modal-demo {
  font-family: sans-serif;
}

.modal-demo__open {
  padding: 10px 20px;
  border: 0;
  border-radius: 8px;
  background: #0017c1;
  color: #fff;
  font-size: 15px;
  cursor: pointer;
}

.modal-demo__open:hover {
  background: #00118f;
}

.modal-demo__overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.5);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
  z-index: 100;
}

/* hidden 属性の既定スタイル(display: none)は、上の display: flex より
   詳細度が同じため後勝ちで上書きされてしまう。hidden のときは明示的に隠す */
.modal-demo__overlay[hidden] {
  display: none;
}

.modal-demo__window {
  width: 100%;
  max-width: 360px;
  background: #fff;
  border-radius: 12px;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
  overflow: hidden;
}

.modal-demo__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 16px;
  border-bottom: 1px solid #eee;
}

.modal-demo__header h2 {
  margin: 0;
  font-size: 17px;
}

.modal-demo__close {
  border: 0;
  background: transparent;
  font-size: 20px;
  line-height: 1;
  cursor: pointer;
  padding: 4px 8px;
}

.modal-demo__body {
  padding: 16px;
}

.modal-demo__body p {
  margin: 0;
  font-size: 14px;
  color: #333;
}

.modal-demo__footer {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  padding: 12px 16px;
  border-top: 1px solid #eee;
}

.modal-demo__cancel,
.modal-demo__confirm {
  padding: 8px 16px;
  border-radius: 6px;
  font-size: 14px;
  cursor: pointer;
}

.modal-demo__cancel {
  border: 1px solid #999;
  background: #fff;
}

.modal-demo__confirm {
  border: 0;
  background: #0017c1;
  color: #fff;
}
JS
document.querySelectorAll("[data-modal-open]").forEach((trigger) => {
  trigger.addEventListener("click", () => {
    const modal = document.getElementById(trigger.getAttribute("data-modal-open"));
    if (modal) modal.hidden = false;
  });
});

document.querySelectorAll(".modal-demo__overlay").forEach((overlay) => {
  overlay.addEventListener("click", (event) => {
    if (event.target === overlay) overlay.hidden = true;
  });
});

document.querySelectorAll("[data-modal-close]").forEach((button) => {
  button.addEventListener("click", () => {
    button.closest(".modal-demo__overlay").hidden = true;
  });
});

document.addEventListener("keydown", (event) => {
  if (event.key === "Escape") {
    document.querySelectorAll(".modal-demo__overlay").forEach((overlay) => {
      overlay.hidden = true;
    });
  }
});