How to Create a Modal Popup in WordPress Without a Plugin (HTML & CSS)

Installing a plugin for every small functionality requirement adds maintenance overhead, potential compatibility conflicts, and weight to the WordPress installation. For sites that only need a simple wordpress modal popup without plugin — a cookie notice, a simple announcement, a video lightbox, or a lead capture form on a single page — a clean HTML, CSS, and JavaScript implementation is often the more appropriate solution: no plugin dependency, no update management, and complete control over the code and behaviour.

This tutorial explains exactly how to create popup in wordpress without plugin — covering the complete HTML structure, CSS styling with backdrop overlay and animation, JavaScript open/close logic, auto-open timing, and a simple exit-intent implementation. The final result is a fully functional, responsive modal popup that can be added to any WordPress page, post, or theme template.

When to Skip the Plugin (and When Not To)

A manual implementation is appropriate for:

  • Simple announcement, cookie notice, or video lightbox on a single page
  • Themes or page builders where adding custom HTML/CSS/JS is straightforward
  • Sites that want to avoid plugin overhead for a one-off popup requirement
  • Developers comfortable with HTML, CSS, and basic JavaScript

A popup plugin is the better choice for:

  • Email list building requiring ESP (Mailchimp, ConvertKit) integration
  • Multiple popups across the site with different targeting rules
  • Exit-intent logic requiring reliable cross-browser cursor tracking
  • A/B testing and conversion tracking
  • Non-developers who need a visual builder interface

How a Modal Popup Works

A modal popup consists of three elements:

  1. A backdrop overlay — a semi-transparent dark layer covering the full viewport, positioned behind the modal and above the page content.
  2. The modal container — the white box centred on screen containing the popup content (text, image, form, video).
  3. JavaScript show/hide logic — adding and removing a CSS class to make the popup visible or hidden, triggered by a button click, time delay, or scroll event.

The hidden state uses display:none or visibility:hidden; the visible state uses display:flex or visibility:visible. An animation transitions between states for a smooth appearance.

Step 1: Add the HTML Markup

Add the following HTML to your WordPress page or post using the Custom HTML block in the Gutenberg block editor, or in your theme’s header.php / footer.php template file if you want the popup available site-wide. The markup consists of the overlay, the modal box, a close button, and the popup content.

<!-- Modal Popup Overlay -->
<div id="wp-modal-overlay" role="dialog" aria-modal="true" aria-labelledby="wp-modal-title">
  <div id="wp-modal-box">

    <!-- Close button -->
    <button id="wp-modal-close" aria-label="Close popup">&times;</button>

    <!-- Popup content -->
    <h2 id="wp-modal-title">Get Our Free Guide</h2>
    <p>Enter your email below and we'll send the guide straight to your inbox.</p>

    <form id="wp-modal-form">
      <input type="email" placeholder="Your email address" required
        style="width:100%;padding:12px 14px;border:2px solid #e2e8f0;
        border-radius:8px;font-size:15px;margin-bottom:12px;box-sizing:border-box;" />
      <button type="submit"
        style="width:100%;padding:13px;background:#4f46e5;color:#fff;
        border:none;border-radius:8px;font-size:15px;font-weight:600;cursor:pointer;">
        Send Me the Guide
      </button>
    </form>

  </div>
</div>

<!-- Trigger button -->
<button id="wp-modal-trigger">Get the Free Guide</button>

Accessibility note: The role="dialog", aria-modal="true", and aria-labelledby attributes are included to make the modal accessible to screen readers. The close button uses aria-label for screen reader users.

Step 2: Add the CSS Styles

Add the following CSS in one of these locations:

  • WordPress Customizer → Additional CSS (for site-wide popup available across all pages)
  • Theme editor → style.css (for theme-level popup)
  • A Custom HTML block with a <style> tag on a specific page
/* === Overlay === */
#wp-modal-overlay {
  display: none; /* Hidden by default */
  position: fixed;
  inset: 0; /* top:0; right:0; bottom:0; left:0 */
  background: rgba(0, 0, 0, 0.6);
  z-index: 99999;
  align-items: center;
  justify-content: center;
  padding: 20px;
  box-sizing: border-box;
}

/* Active state - shown via JS */
#wp-modal-overlay.is-open {
  display: flex;
  animation: fadeInOverlay 0.25s ease;
}

@keyframes fadeInOverlay {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* === Modal Box === */
#wp-modal-box {
  background: #ffffff;
  border-radius: 14px;
  padding: 36px 32px;
  max-width: 500px;
  width: 100%;
  position: relative;
  box-shadow: 0 20px 60px rgba(0,0,0,0.25);
  animation: slideUpModal 0.3s ease;
}

@keyframes slideUpModal {
  from { transform: translateY(30px); opacity: 0; }
  to   { transform: translateY(0);    opacity: 1; }
}

/* === Close Button === */
#wp-modal-close {
  position: absolute;
  top: 14px;
  right: 16px;
  background: none;
  border: none;
  font-size: 24px;
  color: #64748b;
  cursor: pointer;
  line-height: 1;
  padding: 4px 8px;
  border-radius: 4px;
  transition: background 0.2s;
}

#wp-modal-close:hover {
  background: #f1f5f9;
  color: #1a1a2e;
}

/* === Heading & Text === */
#wp-modal-title {
  font-size: 22px;
  font-weight: 700;
  color: #1a1a2e;
  margin: 0 0 10px;
}

#wp-modal-box p {
  color: #64748b;
  font-size: 15px;
  margin: 0 0 20px;
}

/* === Trigger Button === */
#wp-modal-trigger {
  background: #4f46e5;
  color: #fff;
  border: none;
  padding: 14px 28px;
  font-size: 16px;
  font-weight: 600;
  border-radius: 8px;
  cursor: pointer;
  transition: background 0.2s;
}

#wp-modal-trigger:hover {
  background: #4338ca;
}

Step 3: Add the JavaScript

Add the following JavaScript either in a Custom HTML block on the page (inside <script> tags), or in your theme’s footer.php before the closing </body> tag, or via Appearance → Theme Editor → footer.php.

<script>
(function() {
  var overlay  = document.getElementById('wp-modal-overlay');
  var closeBtn = document.getElementById('wp-modal-close');
  var trigger  = document.getElementById('wp-modal-trigger');

  // Open popup
  function openModal() {
    overlay.classList.add('is-open');
    document.body.style.overflow = 'hidden'; // prevent background scroll
  }

  // Close popup
  function closeModal() {
    overlay.classList.remove('is-open');
    document.body.style.overflow = '';
  }

  // Trigger button click opens popup
  if (trigger) {
    trigger.addEventListener('click', openModal);
  }

  // Close button click closes popup
  if (closeBtn) {
    closeBtn.addEventListener('click', closeModal);
  }

  // Clicking the overlay background (outside modal box) closes popup
  if (overlay) {
    overlay.addEventListener('click', function(e) {
      if (e.target === overlay) closeModal();
    });
  }

  // Pressing Escape key closes popup
  document.addEventListener('keydown', function(e) {
    if (e.key === 'Escape') closeModal();
  });

})();
</script>

What this JavaScript does: Opens the popup when the trigger button is clicked, closes it when the × button is clicked, closes it when clicking outside the modal box, and closes it when the Escape key is pressed. The body overflow hidden prevents the page behind the overlay from scrolling while the popup is open.

Step 4: Add a Trigger Button (or Link)

The trigger button (id="wp-modal-trigger") is already included in the HTML markup above. To trigger the popup from any other button, link, or element on the page, add the same id or modify the JavaScript to use a different selector or class. For example, to trigger from any element with the class open-popup:

// Trigger from any element with class "open-popup"
document.querySelectorAll('.open-popup').forEach(function(el) {
  el.addEventListener('click', openModal);
});

In the Gutenberg editor, add a Button block and add the CSS class open-popup in the block’s Advanced settings under “Additional CSS class(es)” — the button will then open the popup on click without any additional code.

Advanced: Auto-Open After a Time Delay

To open the popup automatically after a visitor has been on the page for a set number of seconds, add the following snippet to the JavaScript (after the closeModal function definition):

// Auto-open after 5 seconds (5000 milliseconds)
// Change 5000 to adjust the delay
setTimeout(function() {
  // Only auto-open if popup has not already been seen (uses sessionStorage)
  if (!sessionStorage.getItem('wp-modal-seen')) {
    openModal();
    sessionStorage.setItem('wp-modal-seen', '1');
  }
}, 5000);

The sessionStorage check ensures the auto-open only fires once per browser session — the popup will not re-appear if the visitor navigates to another page and returns during the same session. Replace sessionStorage with localStorage to suppress it across all future sessions (until the visitor clears their browser storage).

Basic Exit-Intent Detection

Exit-intent fires the popup when the visitor moves their cursor toward the top of the browser viewport (toward the address bar or close button). Add this after the auto-open code:

// Exit-intent: fires when cursor moves above top 20px of viewport
document.addEventListener('mouseleave', function(e) {
  if (e.clientY <= 20 && !sessionStorage.getItem('wp-modal-exit-shown')) {
    openModal();
    sessionStorage.setItem('wp-modal-exit-shown', '1');
  }
});

Note: The mouseleave event is the most widely used cross-browser exit-intent signal, but it does not work on mobile devices (no cursor). For mobile, use a scroll-up trigger (scrolling back toward the top of the page) as an alternative exit-intent signal.

Scroll-Depth Trigger

To open the popup when the visitor has scrolled 50% of the page:

// Open popup when visitor scrolls 50% of the page
var scrollTriggered = false;
window.addEventListener('scroll', function() {
  if (scrollTriggered) return;
  var scrolled = (window.scrollY / (document.body.scrollHeight - window.innerHeight)) * 100;
  if (scrolled >= 50) {
    openModal();
    scrollTriggered = true;
  }
});

Complete Code: All Together

Here is the full implementation in a single Custom HTML block — HTML, CSS in a <style> tag, and JavaScript in a <script> tag — ready to paste into a Gutenberg Custom HTML block on any WordPress page:

<style>
#wp-modal-overlay{display:none;position:fixed;inset:0;background:rgba(0,0,0,.6);z-index:99999;align-items:center;justify-content:center;padding:20px;box-sizing:border-box}
#wp-modal-overlay.is-open{display:flex;animation:fadeInOverlay .25s ease}
@keyframes fadeInOverlay{from{opacity:0}to{opacity:1}}
#wp-modal-box{background:#fff;border-radius:14px;padding:36px 32px;max-width:500px;width:100%;position:relative;box-shadow:0 20px 60px rgba(0,0,0,.25);animation:slideUpModal .3s ease}
@keyframes slideUpModal{from{transform:translateY(30px);opacity:0}to{transform:translateY(0);opacity:1}}
#wp-modal-close{position:absolute;top:14px;right:16px;background:none;border:none;font-size:24px;color:#64748b;cursor:pointer;padding:4px 8px;border-radius:4px;transition:background .2s}
#wp-modal-close:hover{background:#f1f5f9;color:#1a1a2e}
#wp-modal-title{font-size:22px;font-weight:700;color:#1a1a2e;margin:0 0 10px}
#wp-modal-box p{color:#64748b;font-size:15px;margin:0 0 20px}
#wp-modal-trigger{background:#4f46e5;color:#fff;border:none;padding:14px 28px;font-size:16px;font-weight:600;border-radius:8px;cursor:pointer;transition:background .2s}
#wp-modal-trigger:hover{background:#4338ca}
</style>

<!-- Popup Overlay -->
<div id="wp-modal-overlay" role="dialog" aria-modal="true" aria-labelledby="wp-modal-title">
  <div id="wp-modal-box">
    <button id="wp-modal-close" aria-label="Close popup">&times;</button>
    <h2 id="wp-modal-title">Get Our Free Guide</h2>
    <p>Enter your email and we'll send it straight to your inbox.</p>
    <form>
      <input type="email" placeholder="Your email address" required
        style="width:100%;padding:12px 14px;border:2px solid #e2e8f0;border-radius:8px;font-size:15px;margin-bottom:12px;box-sizing:border-box;">
      <button type="submit" style="width:100%;padding:13px;background:#4f46e5;color:#fff;border:none;border-radius:8px;font-size:15px;font-weight:600;cursor:pointer;">Send Me the Guide</button>
    </form>
  </div>
</div>

<!-- Trigger Button -->
<button id="wp-modal-trigger">Get the Free Guide</button>

<script>
(function(){
  var overlay=document.getElementById('wp-modal-overlay');
  var closeBtn=document.getElementById('wp-modal-close');
  var trigger=document.getElementById('wp-modal-trigger');
  function openModal(){overlay.classList.add('is-open');document.body.style.overflow='hidden';}
  function closeModal(){overlay.classList.remove('is-open');document.body.style.overflow='';}
  if(trigger)trigger.addEventListener('click',openModal);
  if(closeBtn)closeBtn.addEventListener('click',closeModal);
  if(overlay)overlay.addEventListener('click',function(e){if(e.target===overlay)closeModal();});
  document.addEventListener('keydown',function(e){if(e.key==='Escape')closeModal();});
  // Auto-open after 5 seconds (once per session)
  setTimeout(function(){
    if(!sessionStorage.getItem('wp-modal-seen')){openModal();sessionStorage.setItem('wp-modal-seen','1');}
  },5000);
})();
</script>

Frequently Asked Questions

Will this popup work with all WordPress themes?

Yes — the HTML, CSS, and JavaScript in this tutorial use standard web APIs that work in all modern browsers regardless of the WordPress theme. The popup overlay uses position:fixed with a high z-index (99999) to appear above all theme content. If a theme element has a higher z-index (rare but possible for sticky headers or megamenus), increase the overlay’s z-index to 999999 to ensure it appears on top.

How do I add this popup to multiple pages?

Add the HTML markup in the theme’s footer.php template file (inside the closing </body> tag) to make it available on all pages. Add the CSS in the WordPress Customizer under Additional CSS. Add the JavaScript before </body> in footer.php. Using a child theme prevents the changes from being lost on theme updates. For a large number of pages or complex targeting rules, a popup plugin is a more maintainable approach.

How do I connect the form to Mailchimp or another email service?

The form in this tutorial is a front-end HTML form with no backend processing. To connect it to an email service, replace the form with the embed code provided by your email service (Mailchimp, ConvertKit, Brevo, etc.) — most provide an HTML form embed that can be pasted directly into the popup’s content area. Alternatively, embed a Contact Form 7 shortcode in the popup content area and configure Contact Form 7’s email platform integration. For reliable form-to-ESP integration, a popup plugin with native email platform connectivity is the more appropriate solution.

Need More Advanced Popup Features?

For email platform integration, A/B testing, and advanced targeting rules, consider a dedicated popup plugin.

Try Popup Maker Free →

Mark Henry's avatar
Written by

Mark Henry

Mark Henry is a lead content creator and WordPress expert at ShopCentral.