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:
| Event | When |
|---|---|
show.bs.modal | Before modal opens (cancelable) |
shown.bs.modal | After modal fully opens |
hide.bs.modal | Before modal closes (cancelable) |
hidden.bs.modal | After 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
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.