本文へスキップ

モーダル

ボタンでフェード+スケールインしながらモーダルが中央に開く。背景クリック・×ボタン・Escキーで閉じる。

プレビュー

クリック

操作クリック/タップで動きます。

実装メモ

プレビューは枠内に収めるため、オーバーレイを position:absolute(ステージ基準)で実装しています。実際のページで画面全体に重ねるときは position:fixed に変え、body 直下など高い階層へ置いてください。

使いどころ

「はい/いいえ」のような単純な確認や、削除などの取り消せない操作の最終確認に向く。画面全体を暗くして視線をモーダルへ強制的に集める働きがあるため、ユーザーに必ず読んでほしい・必ず判断してほしい内容だけに使う。逆に、入力項目が5個を超えるような長いフォームや、複数ステップにまたがる作業をモーダルに詰め込むのは避ける。狭い枠の中でスクロールが発生し、下書きの保存もしづらく、閉じ忘れると入力内容が消える。そうした作業は専用ページかドロワーに任せた方がよい。また、通知だけが目的でユーザーの判断を必要としない場合はトーストで足りる。モーダルはユーザーの操作を完全に止める最も強い割り込みなので、1画面で同時に2つ開かないこと、そして開いた理由に対応する選択肢(OK/キャンセルなど)を必ず用意することが前提になる。

仕組み

backdrop と modal を別要素として重ね、開閉は is-open クラスの付け外しだけで行う。backdrop は opacity、modal は transform: scale() と opacity をトランジションさせ、cubic-bezier(0.34,1.36,0.64,1) で終盤にわずかに伸びてから収まる「弾む」イージングを使っている。scale/opacity/transform だけをアニメーションさせているのはレイアウト再計算を避けるため。開くときは FOCUSABLE セレクタで modal 内の操作可能要素を集めて先頭へフォーカスを移し、開く直前の document.activeElement を trigger として保持しておき、閉じたときにそこへフォーカスを戻す。Tab キーは document 側の keydown で監視し、最初と最後の要素で折り返すことでフォーカスをモーダルの外へ逃さない、いわゆるフォーカストラップを自前で実装している。

実装の注意

最大の落とし穴は、閉じている間も .modal の中身がキーボードでフォーカス可能なままなこと。非表示は opacity と pointer-events だけで行っており display:none にしていないため、Tab トラップは isOpen が true のときしか働かず、モーダルを開く前や閉じた後にページ本体を Tab で辿ると、見えないはずのキャンセル/OKボタンにフォーカスが飛んでしまう。閉じるタイミングで inert 属性を付ける(対応していない環境向けには中の要素へ一括で tabindex="-1" を設定し、開くときに戻す)対策が要る。また、Escape のキー監視は isOpen を見ずに常に closeModal() を呼んでいるため、他のオーバーレイと共存させる画面では想定外のモーダルまで閉じないよう、実装を分けるときは「今アクティブなオーバーレイはどれか」を管理する仕組みに直す必要がある。

アクセシビリティ

WAI-ARIA の Dialog (Modal) パターンに沿って role="dialog"・aria-modal="true"・aria-labelledby でタイトルと紐付け、開いたら最初の操作要素へフォーカスを移し、閉じたら開く前にフォーカスがあった要素へ戻す、という一連の型は実装済み。ただし aria-hidden="true" を付けた要素の中に focusable な要素が残っているのは ARIA の仕様違反で、支援技術によっては aria-hidden 自体を無視してしまう。閉じるときは aria-hidden の切り替えだけでなく、上記の pitfalls にある inert 化とセットで行うべきだ。また aria-modal="true" は背景コンテンツを支援技術から隠す指示だが実装差があるため、可能なら背景側にも inert を当てて確実に読み上げ対象から外す。prefers-reduced-motion ではバックドロップとモーダル両方のトランジションを丸ごと止めており、動きに敏感なユーザーへの配慮もできている。

調整して使う

触りやすいのは3つ。まずイージングで、cubic-bezier(0.34,1.36,0.64,1) の「弾み」を弱めたいときは overshoot の少ない ease-out に変えると落ち着いた印象になる。次にバックドロップの濃さ(既定 rgba(44,40,35,0.45))で、フォームなど注視させたい内容ほど濃く、軽い確認ほど薄くする。最後にモーダルの出現位置とサイズで、デモは中央固定・幅210pxだが、フォームを載せるなら幅を広げて上寄りに配置すると入力中にソフトウェアキーボードで隠れにくい。なお、背景クリックとEscapeでの閉鎖は「取り消し可能な操作」向けの挙動なので、削除の最終確認のように誤操作を避けたい場面ではこの2つを外し、明示的なボタン操作だけで閉じるようにするとよい。

コード(コピーして使えます)

<div class="stage" id="stage">
  <button class="open-btn" id="openModal">モーダルを開く</button>

  <div class="backdrop" id="backdrop" aria-hidden="true"></div>
  <div class="modal" id="modal" role="dialog" aria-modal="true" aria-labelledby="modalTitle" aria-hidden="true">
    <div class="modal-header">
      <p class="modal-title" id="modalTitle">確認</p>
      <button class="close-btn" id="closeModal" aria-label="閉じる">
        <svg width="16" height="16" viewBox="0 0 16 16" fill="none">
          <line x1="3" y1="3" x2="13" y2="13" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
          <line x1="13" y1="3" x2="3" y2="13" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
        </svg>
      </button>
    </div>
    <p class="modal-body">この操作を続けますか?<br>変更内容は保存されます。</p>
    <div class="modal-footer">
      <button class="btn-cancel" id="cancelModal">キャンセル</button>
      <button class="btn-ok">OK</button>
    </div>
  </div>
</div>

タグ

同じカテゴリ「通知・オーバーレイ」のほかのパーツ

関連パーツ