Modals are everywhere in web apps — confirmations, forms, alerts, image previews. Bootstrap 5 modals are solid once you understand how they work. Here's everything.

Basic Structure

<!-- Trigger button -->
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#myModal">
  Open Modal
</button>

<!-- Modal -->
<div class="modal fade" id="myModal" tabindex="-1" aria-labelledby="myModalLabel" aria-hidden="true">
  <div class="modal-dialog">
    <div class="modal-content">

      <div class="modal-header">
        <h5 class="modal-title" id="myModalLabel">Modal Title</h5>
        <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
      </div>

      <div class="modal-body">
        Your modal content goes here.
      </div>

      <div class="modal-footer">
        <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
        <button type="button" class="btn btn-primary">Save</button>
      </div>

    </div>
  </div>
</div>

The id on the modal must match data-bs-target="#myModal" on the trigger. That connection is all Bootstrap needs — no JavaScript required for basic open/close.

JavaScript API

// Open
const modal = new bootstrap.Modal(document.getElementById('myModal'))
modal.show()

// Close
modal.hide()

// Toggle
modal.toggle()

// Get existing instance (if already initialized)
const existing = bootstrap.Modal.getInstance(document.getElementById('myModal'))
if (existing) existing.hide()

Create the instance once and reuse it. Creating a new instance each time works but wastes memory.

Modal Sizes

<!-- Small -->
<div class="modal-dialog modal-sm">

<!-- Default (no class needed) -->
<div class="modal-dialog">

<!-- Large -->
<div class="modal-dialog modal-lg">

<!-- Extra large -->
<div class="modal-dialog modal-xl">

<!-- Full screen -->
<div class="modal-dialog modal-fullscreen">

<!-- Full screen only on mobile -->
<div class="modal-dialog modal-fullscreen-sm-down">

Form Inside Modal — The Right Way

Forms inside modals are one of the most common patterns. Here's how to do it properly:

<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addUserModal">
  Add User
</button>

<div class="modal fade" id="addUserModal" tabindex="-1">
  <div class="modal-dialog">
    <div class="modal-content">
      <div class="modal-header border-0">
        <h5 class="modal-title fw-bold">Add New User</h5>
        <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
      </div>
      <div class="modal-body">
        <div class="mb-3">
          <label class="form-label">Full Name</label>
          <input type="text" class="form-control" id="userName" placeholder="John Smith">
        </div>
        <div class="mb-3">
          <label class="form-label">Email</label>
          <input type="email" class="form-control" id="userEmail" placeholder="john@example.com">
        </div>
        <div class="mb-0">
          <label class="form-label">Role</label>
          <select class="form-select" id="userRole">
            <option>Admin</option>
            <option>Editor</option>
            <option>Viewer</option>
          </select>
        </div>
      </div>
      <div class="modal-footer border-0">
        <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
        <button type="button" class="btn btn-primary" id="saveUserBtn">Add User</button>
      </div>
    </div>
  </div>
</div>

<script>
  document.getElementById('saveUserBtn').addEventListener('click', function() {
    const name = document.getElementById('userName').value
    const email = document.getElementById('userEmail').value

    if (!name || !email) {
      alert('Please fill in all fields')
      return
    }

    // Process form data here
    console.log({ name, email })

    // Close modal
    bootstrap.Modal.getInstance(document.getElementById('addUserModal')).hide()
  })

  // Clear form when modal closes
  document.getElementById('addUserModal').addEventListener('hidden.bs.modal', function() {
    document.getElementById('userName').value = ''
    document.getElementById('userEmail').value = ''
  })
</script>

The hidden.bs.modal event fires after the modal is fully hidden — good place to reset forms.

Passing Data to a Modal

Classic pattern — clicking different rows in a table opens the same modal with different data:

<button class="btn btn-sm btn-outline-primary"
  data-bs-toggle="modal"
  data-bs-target="#editModal"
  data-user-id="123"
  data-user-name="Gagan Singh">
  Edit
</button>

<script>
  document.getElementById('editModal').addEventListener('show.bs.modal', function(event) {
    const button = event.relatedTarget
    const userId = button.getAttribute('data-user-id')
    const userName = button.getAttribute('data-user-name')

    // Populate modal fields
    document.getElementById('editUserId').value = userId
    document.getElementById('editUserName').value = userName
  })
</script>

event.relatedTarget is the button that triggered the modal. Read data attributes from it to populate the modal content. This is much cleaner than a separate modal per row.

Modal Events

Bootstrap 5 fires these events on the modal element:

EventWhen
show.bs.modalBefore modal opens (cancelable)
shown.bs.modalAfter modal fully opens
hide.bs.modalBefore modal closes (cancelable)
hidden.bs.modalAfter modal fully closes
const modal = document.getElementById('myModal')

modal.addEventListener('shown.bs.modal', () => {
  // Focus first input after modal opens
  modal.querySelector('input')?.focus()
})

modal.addEventListener('hidden.bs.modal', () => {
  // Reset form when modal closes
  modal.querySelector('form')?.reset()
})

Static Backdrop (Prevent Close on Click Outside)

<div class="modal fade" id="myModal" data-bs-backdrop="static" data-bs-keyboard="false">

data-bs-backdrop="static" — clicking the backdrop does nothing. data-bs-keyboard="false" — pressing ESC does nothing.

Use this for important forms where accidental dismissal would lose data.

Frequently Asked Questions

Get the element and create a Modal instance: const modal = new bootstrap.Modal(document.getElementById('myModal')); modal.show(). Or use getInstance if it's already initialized: bootstrap.Modal.getInstance(element).show().
Get the modal instance and call hide: const modal = bootstrap.Modal.getInstance(document.getElementById('myModal')); modal.hide(). Put this in your form submit handler after processing the form data.
Add data-bs-backdrop='static' to the modal element: <div class='modal fade' data-bs-backdrop='static' data-bs-keyboard='false'>. This prevents closing on backdrop click and ESC key. Users must use the close button.
Listen for the show.bs.modal event which provides a relatedTarget (the trigger button). Read data attributes from it: modal.addEventListener('show.bs.modal', e => { const btn = e.relatedTarget; const id = btn.getAttribute('data-item-id'); }). Then populate modal content from those values.

Need a Full Bootstrap 5 Admin Dashboard?

Get a complete Angular 21 + Bootstrap 5 dashboard with 50+ components — built by the same team behind BootstrapPlanet.

Browse Templates →

Use code FIRST30 for 30% off your first purchase.

Related Components