Bulma Modal
The Modal component in Bulma creates overlay dialog boxes that appear above the main content. It's perfect for confirmations, forms, alerts, and other content that requires user attention without navigating away from the current page.
Basic Modal Structure
A modal consists of three main parts: backdrop, card container, and content:
<!-- Modal backdrop (overlay) -->
<div class="modal">
<!-- Background overlay -->
<div class="modal-background"></div>
<!-- Modal content container -->
<div class="modal-card">
<!-- Header (optional) -->
<header class="modal-card-head">
<p class="modal-card-title">Modal Title</p>
<button class="delete" aria-label="close"></button>
</header>
<!-- Body content -->
<section class="modal-card-body">
<!-- Content goes here -->
Modal content goes here.
</section>
<!-- Footer (optional) -->
<footer class="modal-card-foot">
<button class="button is-success">Save changes</button>
<button class="button">Cancel</button>
</footer>
</div>
</div>Modal Activation
Modals require JavaScript to toggle the is-active class:
<!-- HTML Structure -->
<!-- Trigger Button -->
<button class="button is-primary" id="open-modal">
Open Modal
</button>
<!-- Modal (initially hidden) -->
<div class="modal" id="example-modal">
<div class="modal-background"></div>
<div class="modal-card">
<header class="modal-card-head">
<p class="modal-card-title">Example Modal</p>
<button class="delete" aria-label="close" id="close-modal"></button>
</header>
<section class="modal-card-body">
Modal content here.
</section>
</div>
</div>
<!-- JavaScript -->
<script>
const modal = document.getElementById('example-modal');
const openBtn = document.getElementById('open-modal');
const closeBtn = document.getElementById('close-modal');
// Open modal
openBtn.addEventListener('click', () => {
modal.classList.add('is-active');
});
// Close modal with X button
closeBtn.addEventListener('click', () => {
modal.classList.remove('is-active');
});
// Close modal when clicking background
modal.querySelector('.modal-background').addEventListener('click', () => {
modal.classList.remove('is-active');
});
// Close modal with Escape key
document.addEventListener('keydown', (event) => {
if (event.key === 'Escape' && modal.classList.contains('is-active')) {
modal.classList.remove('is-active');
}
});
</script>Modal Card Structure
| Element | Class | Purpose |
|---|---|---|
| Container | .modal | Main wrapper |
| Backdrop | .modal-background | Overlay background |
| Content Box | .modal-card | Modal content container |
| Header | .modal-card-head | Title and close button |
| Body | .modal-card-body | Main content area |
| Footer | .modal-card-foot | Action buttons |
Alternative Modal Content
For simpler modals, you can use .modal-content instead of .modal-card:
<!-- Simple modal content -->
<div class="modal">
<div class="modal-background"></div>
<div class="modal-content">
<!-- Any content you want -->
<div class="box">
<p>Simple modal content</p>
</div>
</div>
<button class="modal-close is-large" aria-label="close"></button>
</div>
<!-- With image content -->
<div class="modal">
<div class="modal-background"></div>
<div class="modal-content">
<p class="image is-4by3">
<img src="large-image.jpg" alt="Large image">
</p>
</div>
<button class="modal-close is-large" aria-label="close"></button>
</div>Modal Sizes
Control modal width with size modifiers:
<!-- Small modal -->
<div class="modal">
<div class="modal-background"></div>
<div class="modal-card">
<header class="modal-card-head">
<p class="modal-card-title">Small Modal</p>
</header>
<section class="modal-card-body">
Small modal content
</section>
</div>
</div>
<!-- Medium modal (default) -->
<div class="modal">
<!-- ... -->
</div>
<!-- Large modal -->
<div class="modal is-large">
<div class="modal-background"></div>
<div class="modal-card">
<header class="modal-card-head">
<p class="modal-card-title">Large Modal</p>
</header>
<section class="modal-card-body">
Large modal content
</section>
</div>
</div>
<!-- Full-screen modal -->
<div class="modal is-full-screen">
<div class="modal-background"></div>
<div class="modal-card">
<header class="modal-card-head">
<p class="modal-card-title">Full Screen Modal</p>
</header>
<section class="modal-card-body">
Full screen content
</section>
</div>
</div>Modal with Form
Common use case: form inside a modal:
<div class="modal">
<div class="modal-background"></div>
<div class="modal-card">
<header class="modal-card-head">
<p class="modal-card-title">Contact Form</p>
<button class="delete" aria-label="close"></button>
</header>
<section class="modal-card-body">
<form>
<div class="field">
<label class="label">Name</label>
<div class="control">
<input class="input" type="text" placeholder="Your name">
</div>
</div>
<div class="field">
<label class="label">Email</label>
<div class="control">
<input class="input" type="email" placeholder="Your email">
</div>
</div>
<div class="field">
<label class="label">Message</label>
<div class="control">
<textarea class="textarea" placeholder="Your message"></textarea>
</div>
</div>
</form>
</section>
<footer class="modal-card-foot">
<button class="button is-success">Send Message</button>
<button class="button" id="cancel-form">Cancel</button>
</footer>
</div>
</div>JavaScript Modal Management
For better code organization, create a modal manager:
// modalManager.js
class ModalManager {
constructor() {
this.modals = new Map();
this.init();
}
init() {
// Add event listeners for all modals
document.querySelectorAll('.modal').forEach(modal => {
const modalId = modal.id || 'modal-' + Date.now();
modal.id = modalId;
this.registerModal(modalId, modal);
});
// Close modals on background click
document.addEventListener('click', (e) => {
if (e.target.classList.contains('modal-background')) {
this.close(e.target.closest('.modal').id);
}
});
// Close modals on escape key
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
this.closeAll();
}
});
}
registerModal(id, element) {
this.modals.set(id, element);
}
open(id) {
const modal = this.modals.get(id);
if (modal) {
modal.classList.add('is-active');
document.body.classList.add('is-clipped');
}
}
close(id) {
const modal = this.modals.get(id);
if (modal) {
modal.classList.remove('is-active');
document.body.classList.remove('is-clipped');
}
}
closeAll() {
this.modals.forEach(modal => {
modal.classList.remove('is-active');
});
document.body.classList.remove('is-clipped');
}
}
// Usage
const modalManager = new ModalManager();
// Open modal
document.getElementById('open-btn').addEventListener('click', () => {
modalManager.open('my-modal');
});
// Close modal
document.getElementById('close-btn').addEventListener('click', () => {
modalManager.close('my-modal');
});Modal with Animation
Add CSS animations for smoother transitions:
<!-- CSS Animations -->
<style>
.modal {
opacity: 0;
visibility: hidden;
transition: all 0.3s ease-in-out;
}
.modal.is-active {
opacity: 1;
visibility: visible;
}
.modal-card {
transform: translateY(-20px);
transition: transform 0.3s ease-in-out;
}
.modal.is-active .modal-card {
transform: translateY(0);
}
.modal-background {
transition: opacity 0.3s ease-in-out;
}
</style>
<!-- HTML remains the same -->
<div class="modal" id="animated-modal">
<div class="modal-background"></div>
<div class="modal-card">
<!-- Modal content -->
</div>
</div>Practical Modal Examples
Confirmation Dialog
<div class="modal" id="confirmation-modal">
<div class="modal-background"></div>
<div class="modal-card">
<header class="modal-card-head">
<p class="modal-card-title">Confirm Action</p>
<button class="delete" aria-label="close" data-close="confirmation-modal"></button>
</header>
<section class="modal-card-body">
<div class="content">
<p class="has-text-centered">
<span class="icon is-large has-text-warning mb-3">
<i class="fas fa-exclamation-triangle fa-2x"></i>
</span>
</p>
<h4 class="title is-4 has-text-centered">Are you sure?</h4>
<p class="has-text-centered">
This action cannot be undone. All data will be permanently deleted.
</p>
</div>
</section>
<footer class="modal-card-foot is-justify-content-center">
<button class="button is-danger" id="confirm-delete">
<span class="icon"><i class="fas fa-trash"></i></span>
<span>Delete</span>
</button>
<button class="button" data-close="confirmation-modal">
Cancel
</button>
</footer>
</div>
</div>
<script>
document.getElementById('confirm-delete').addEventListener('click', () => {
// Perform delete action
console.log('Item deleted');
// Close modal
document.getElementById('confirmation-modal').classList.remove('is-active');
// Show success message
alert('Item deleted successfully');
});
</script>Login/Registration Modal
<div class="modal" id="auth-modal">
<div class="modal-background"></div>
<div class="modal-card">
<header class="modal-card-head">
<p class="modal-card-title">Welcome Back</p>
<button class="delete" aria-label="close"></button>
</header>
<section class="modal-card-body">
<!-- Tabs for Login/Register -->
<div class="tabs is-toggle is-fullwidth">
<ul>
<li class="is-active" data-tab="login">
<a>Login</a>
</li>
<li data-tab="register">
<a>Register</a>
</li>
</ul>
</div>
<!-- Login Form -->
<div class="tab-content" id="login">
<form id="login-form">
<div class="field">
<label class="label">Email</label>
<div class="control">
<input class="input" type="email" placeholder="you@example.com" required>
</div>
</div>
<div class="field">
<label class="label">Password</label>
<div class="control">
<input class="input" type="password" placeholder="Your password" required>
</div>
</div>
<div class="field">
<label class="checkbox">
<input type="checkbox">
Remember me
</label>
</div>
</form>
</div>
<!-- Register Form (hidden by default) -->
<div class="tab-content is-hidden" id="register">
<form id="register-form">
<!-- Registration fields -->
</form>
</div>
</section>
<footer class="modal-card-foot">
<button class="button is-primary is-fullwidth" form="login-form">
Sign In
</button>
</footer>
</div>
</div>Image Lightbox Modal
<!-- Thumbnail Grid -->
<div class="columns is-multiline">
<div class="column is-one-quarter">
<a class="lightbox-trigger" data-image="image1.jpg" data-title="Image 1">
<img src="thumb1.jpg" alt="Thumbnail 1">
</a>
</div>
<!-- More thumbnails -->
</div>
<!-- Lightbox Modal -->
<div class="modal" id="lightbox-modal">
<div class="modal-background"></div>
<div class="modal-content">
<div class="box">
<figure class="image">
<img id="lightbox-image" src="" alt="">
</figure>
<p class="has-text-centered mt-3" id="lightbox-title"></p>
</div>
</div>
<button class="modal-close is-large" aria-label="close"></button>
<!-- Navigation buttons -->
<button class="modal-nav prev">
<span class="icon"><i class="fas fa-chevron-left"></i></span>
</button>
<button class="modal-nav next">
<span class="icon"><i class="fas fa-chevron-right"></i></span>
</button>
</div>
<script>
const lightboxTriggers = document.querySelectorAll('.lightbox-trigger');
const lightboxModal = document.getElementById('lightbox-modal');
const lightboxImage = document.getElementById('lightbox-image');
const lightboxTitle = document.getElementById('lightbox-title');
lightboxTriggers.forEach(trigger => {
trigger.addEventListener('click', () => {
const imageSrc = trigger.dataset.image;
const imageTitle = trigger.dataset.title;
lightboxImage.src = imageSrc;
lightboxTitle.textContent = imageTitle;
lightboxModal.classList.add('is-active');
});
});
</script>CSS Variables for Customization
| Variable | Description | Default Value |
|---|---|---|
$modal-background-background-color | Backdrop color | rgba($black, 0.86) |
$modal-content-width | Default modal width | 640px |
$modal-content-margin-mobile | Mobile margin | 20px |
$modal-close-dimensions | Close button size | 40px |
$modal-close-right | Close button position | 20px |
$modal-close-top | Close button position | 20px |
$modal-card-spacing | Card spacing | 40px |
$modal-card-head-background-color | Header background | $white-bis |
$modal-card-title-color | Title color | $text-strong |
$modal-card-foot-background-color | Footer background | $white-bis |
Best Practices
- Always provide a way to close the modal (X button, background click, Escape key)
- Use
aria-labelfor close buttons for accessibility - Manage focus properly - trap focus inside modal when open
- Return focus to triggering element when modal closes
- Use appropriate modal sizes for different content types
- Consider mobile users - ensure modals are responsive
- Add animations for better user experience (but keep them subtle)
- Prevent background scrolling when modal is open
- Test with keyboard navigation and screen readers
aria-modal=&qout;true&qout;, aria-labelledby), and keyboard navigation.Up next: Bulma Navbar Component