← 記事一覧に戻る

Bootstrap5で入れ子モーダルを実現する

公開日 2025/07/19更新日 2025/07/19

Bootstrap5では公式にサポートされていない入れ子モーダルを作る

ウェブアプリにおいて、ユーザーからの操作や情報入力を段階的に進めるために、複数のモーダルを重ねて表示したいケースがしばしば存在する。 しかし Bootstrap5 では標準のモーダル機能として入れ子モーダルをサポートしていない。

公式でもUXの関係でサポートしていないと明示されており、モーダルから別のモーダルを呼び出すことは可能だが、元のモーダルは消えてしまう。

Toggle between modals

なぜ消えるか

通常、Bootstrap5のモーダルはdata属性を用いて展開する。

<button class="btn btn-primary" data-bs-target="#modalA" data-bs-toggle="modal">
  Open second modal
</button>

このとき、bootstrap.js で呼ばれる処理の中に、すでに開いているモーダルがある場合は閉じる処理が入っているため、展開中のものは閉じられてしまう。

// avoid conflict when clicking modal toggler while another one is open
const alreadyOpen = SelectorEngine.findOne(OPEN_SELECTOR)
if (alreadyOpen) {
  Modal.getInstance(alreadyOpen).hide()
}

const data = Modal.getOrCreateInstance(target)

data.toggle(this)

js/src/modal.js

そのため、data属性を用いたモーダル展開を使うことはできない。

解決策

代わりにモーダルインスタンスの show() メソッドを使う。

js/src/modal.js

show() には上述の処理は含まれてないため複数のモーダルを同時に表示できる。 あとはモーダルと背景となる backdropz-index を調整してうまく層を作ればいい。

サンプル

以下は jQuery でモーダルの表示トリガーを監視し、モーダルの z-indexbackdrop を管理することで入れ子モーダルを実装している。

js

$(function () {
  $(document).on('click', '.show-modal', function () {
    const nextId = $(this).data('show-modal')
    if (nextId) showModal(nextId)
  })

  function showModal(modalId) {
    const newModal = $('#' + modalId)
    const modalInstance = new bootstrap.Modal(newModal[0])
    modalInstance.show()

    _setupModalZIndex(newModal)
    _setupBackdrop(modalId)
    _cleanupModal(newModal, modalId)
  }

  /**
   * モーダルのz-indexを設定する
   * z-index = 1040 + 10 * 表示中のモーダル数(Bootstrap標準の重なり順に準拠)
   * ex: 1段目: 1040, 2段目: 1050, 3段目: 1060, ...
   */
  function _setupModalZIndex(modal) {
    modal.one('shown.bs.modal', function () {
      const modalCount = $('.modal.show').length
      const zIndex = 1040 + 10 * modalCount
      modal.css('z-index', zIndex)
      modal.data('zIndex', zIndex) // backdropで使うために保存しておく
    })
  }

  /**
   * モーダルbackdropのz-indexを調整する
   * ex: 1段目: 1039, 2段目: 1049, 3段目: 1059, ...
   */
  function _setupBackdrop(modalId) {
    // z-index の設定は shown.bs.modal の後でないと正しい数にならない
    $('#' + modalId).one('shown.bs.modal', function () {
      const modal = $(this)
      const zIndex = modal.data('zIndex')

      const backdrop = $('.modal-backdrop').not('.modal-stack').last()

      backdrop
        .css('z-index', zIndex - 1) // モーダルより1つ下に配置
        .addClass('modal-stack')
        .attr('data-backdrop-for', modalId) // 削除処理のためにモーダルIDで関連付け
    })
  }

  /**
   * モーダルが閉じられたとき(hidden.bs.modal)に、対応するbackdropを削除し、
   * フォーカスを親モーダルかbodyへ逃がす
   */
  function _cleanupModal(modal, modalId) {
    modal.one('hidden.bs.modal', () => {
      $(`.modal-backdrop[data-backdrop-for="${modalId}"]`).remove()

      const openModals = $('.modal.show')
      if (openModals.length > 0) {
        // 親モーダルにフォーカスを当てる
        const lastModal = openModals.last()
        const focusTarget = lastModal
          .find('[autofocus], .btn-close, .btn, input, select')
          .first()
        if (focusTarget.length) focusTarget.trigger('focus')
      } else {
        // 最後のモーダルを閉じた場合、bodyにフォーカスを逃がす(アクセシビリティ対策)
        $(document.activeElement).trigger('blur')
        $('body').trigger('focus')
      }
    })
  }
})

html

<link
  href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css"
  rel="stylesheet"
/>
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>

<button class="btn btn-primary show-modal" data-show-modal="modal1">
  Open Modal 1
</button>

<!-- モーダル1 -->
<div class="modal" id="modal1" tabindex="-1">
  <div class="modal-dialog">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title">Modal 1</h5>
        <button
          type="button"
          class="btn-close"
          data-bs-dismiss="modal"
        ></button>
      </div>
      <div class="modal-body">
        <button class="btn btn-secondary show-modal" data-show-modal="modal2">
          Open Modal 2
        </button>
      </div>
    </div>
  </div>
</div>

<!-- モーダル2 -->
<div class="modal" id="modal2" tabindex="-1">
  <div class="modal-dialog">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title">Modal 2</h5>
        <button
          type="button"
          class="btn-close"
          data-bs-dismiss="modal"
        ></button>
      </div>
      <div class="modal-body">
        <button class="btn btn-secondary show-modal" data-show-modal="modal3">
          Open Modal 3
        </button>
      </div>
    </div>
  </div>
</div>

<!-- モーダル3 -->
<div class="modal" id="modal3" tabindex="-1">
  <div class="modal-dialog">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title">Modal 3</h5>
        <button
          type="button"
          class="btn-close"
          data-bs-dismiss="modal"
        ></button>
      </div>
      <div class="modal-body">
        <button class="btn btn-secondary show-modal" data-show-modal="modal4">
          Open Modal 4
        </button>
      </div>
    </div>
  </div>
</div>

<!-- モーダル4 -->
<div class="modal" id="modal4" tabindex="-1">
  <div class="modal-dialog">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title">Modal 4</h5>
        <button
          type="button"
          class="btn-close"
          data-bs-dismiss="modal"
        ></button>
      </div>
      <div class="modal-body">
        <button class="btn btn-secondary show-modal" data-show-modal="modal5">
          Open Modal 5
        </button>
      </div>
    </div>
  </div>
</div>

<!-- モーダル5 -->
<div class="modal" id="modal5" tabindex="-1">
  <div class="modal-dialog">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title">Modal 5</h5>
        <button
          type="button"
          class="btn-close"
          data-bs-dismiss="modal"
        ></button>
      </div>
      <div class="modal-body">
        <p>Last modal (5)</p>
      </div>
    </div>
  </div>
</div>

モーダルからモーダルを開くたび、モーダルと backdropz-index を調整することで層を形成している。

      _______________
     /               /   Modal 4
    / z-index:1070  /
   /_______________/
------ -index:1069 ----- Backdrop 4


      _______________
     /               /   Modal 3
    / z-index:1060  /
   /_______________/
----- z-index:1059 ----- Backdrop 3


      _______________
     /               /    Modal 2
    / z-index:1050  /
   /_______________/
------ z-index:1049 ----- Backdrop 2


      _______________
     /               /    Modal 1
    / z-index:1040  /
   /_______________/
------ z-index:1039 ----- Backdrop 1

モーダルを呼び出すボタンには

  • show-modal クラス
  • data-show-modal="モーダルのID"

をつける。 モーダル自体は通常の定義と変わらないため、使い心地にも違和感はない。

<button class="btn show-modal" data-show-modal="modal1">Open Modal 1</button>

デモ

<video class="rounded-xl border border-white/10" autoplay muted loop playsinline controls

nested modal demo
nested modal demo