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.

Note: Bulma provides the CSS structure for modals, but you need to implement the JavaScript functionality to show/hide them.

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

ElementClassPurpose
Container.modalMain wrapper
Backdrop.modal-backgroundOverlay background
Content Box.modal-cardModal content container
Header.modal-card-headTitle and close button
Body.modal-card-bodyMain content area
Footer.modal-card-footAction 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

VariableDescriptionDefault Value
$modal-background-background-colorBackdrop colorrgba($black, 0.86)
$modal-content-widthDefault modal width640px
$modal-content-margin-mobileMobile margin20px
$modal-close-dimensionsClose button size40px
$modal-close-rightClose button position20px
$modal-close-topClose button position20px
$modal-card-spacingCard spacing40px
$modal-card-head-background-colorHeader background$white-bis
$modal-card-title-colorTitle color$text-strong
$modal-card-foot-background-colorFooter background$white-bis

Best Practices

  • Always provide a way to close the modal (X button, background click, Escape key)
  • Use aria-label for 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
Accessibility is Key: Ensure your modals are accessible by implementing focus trapping, proper ARIA attributes (aria-modal=&qout;true&qout;, aria-labelledby), and keyboard navigation.

Up next: Bulma Navbar Component