Implementing a Bulma Modal with Vanilla JavaScript: A Step‑by‑Step Guide
Learn how to add a fully functional Bulma modal to your site using plain JavaScript. This guide covers markup, event handling, accessibility, and troubleshooting.
17 Nov 2025, 13:26 UTC

Desired Outcome
By the end of this guide you will have a Bulma modal that opens when a button is clicked, closes when the close button or background is pressed, traps focus while active, and is fully accessible. No external JS libraries are required.
Prerequisites
- Bulma CSS (v0.9.4 or later) loaded in the
<head>of your page. - Basic HTML page structure.
- Knowledge of the
classListAPI and event handling in vanilla JavaScript. - Browser console for debugging.
Markup Structure
The modal must follow Bulma’s required elements. Place the markup after the trigger button so that the script can locate it with querySelector. The following skeleton is complete:
<button id="open-modal" class="button is-primary">Open Modal</button>
<div id="my-modal" class="modal">
<div class="modal-background"></div>
<div class="modal-card">
<header class="modal-card-head">
<p class="modal-card-title">Modal Title</p>
<button class="delete" aria-label="close"></button>
</header>
<section class="modal-card-body">
Modal content goes here.
</section>
<footer class="modal-card-foot">
<button class="button is-success">Save changes</button>
<button class="button">Cancel</button>
</footer>
</div>
</div>
Key points:
- The top‑level
.modalmust exist. - The
.modal-backgroundenables click‑away close. - The
.modal-cardis optional but recommended for a structured dialog. - The
.deletebutton is the close trigger; it carries anaria-label="close"for screen readers.
JavaScript Integration
Attach event listeners to the open button, close button, and background. The script toggles the is-active class, which Bulma uses to show/hide the modal.
// Run in the browser console or a separate JS file
const modal = document.getElementById('my-modal');
const openBtn = document.getElementById('open-modal');
const closeBtn = modal.querySelector('.delete');
const bg = modal.querySelector('.modal-background');
function toggleModal() {
modal.classList.toggle('is-active');
}
openBtn.addEventListener('click', toggleModal);
closeBtn.addEventListener('click', toggleModal);
bg.addEventListener('click', toggleModal);
// Optional: Close with Escape key
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && modal.classList.contains('is-active')) {
toggleModal();
}
});
// Focus trap when modal opens
modal.addEventListener('transitionend', () => {
if (modal.classList.contains('is-active')) {
// Find the first focusable element inside the modal
const focusable = modal.querySelector('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
focusable && focusable.focus();
}
});
Use classList.toggle for brevity; it adds the class if missing and removes it if present. The transitionend listener ensures focus is set after the modal becomes visible.
Accessibility Considerations
- Each close button has
aria-label="close". - The modal’s
.modal-cardhasrole="dialog"andaria-modal="true"implicitly via Bulma; you can add them manually for clarity. - Focus is trapped inside the modal while active. The example above moves focus to the first interactive element.
- Keyboard users can close the modal with
Esc, and clicking the background also closes it.
Expected Checks
- Open the page in a browser and click
Open Modal. The modal should fade in. - Verify that the
is-activeclass appears on#my-modalin the DOM. - Press
Escor click the close button or background; the modal should fade out and the class should be removed. - While the modal is open, tab through interactive elements; focus should not leave the modal.
- Inspect the console for errors; no listeners should be reported as missing.
Recovery Options
Common pitfalls and how to fix them:
- Modal never appears – Ensure the
#my-modalelement exists and that the script runs after the DOM is ready. If you use<script>in the<head>, wrap the code inDOMContentLoaded. - Click‑away doesn’t close – The
.modal-backgroundelement is missing. Add it or attach the listener to the.modalroot instead. - Focus escapes the modal – The focus‑trap code may not find a focusable element. Ensure there is at least one button or input inside the modal.
- Modal stays open after page reload – No state is persisted, so a reload always starts closed. If you need persistence, store a flag in
sessionStorageand applyis-activeon load.
Practical Verification Checklist
| Check | How to Verify |
|---|---|
| Modal opens | Click button, observe fade‑in and is-active class. |
| Modal closes via close button | Click .delete, confirm fade‑out and class removal. |
| Modal closes via background | Click .modal-background, same as above. |
| Escape key works | Press Esc while modal is open. |
| Focus trap | Tab through elements; focus should not leave modal. |
Conclusion
Implementing a Bulma modal with vanilla JavaScript is straightforward once you understand the required markup and the is-active class mechanism. By following the markup, adding minimal JS, and verifying accessibility, you can deliver a polished dialog experience without pulling in heavy frameworks.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.