2026年11月12日
2026年11月12日
How to Add a Modal Dialog in WordPress with the dialog Element
This article is a localized edition of the Japanese original. Read the Japanese original
A modal dialog is UI that sits on top of the page and blocks the background until it is closed. You can use it for notices, image details, confirmation messages and more.
The traditional approach was to hide a div with display: none and toggle it with JavaScript. With that approach, though, you have to write everything yourself: closing with the Esc key, keeping keyboard focus inside the dialog, hiding the background from screen readers, and so on. Today the HTML <dialog> element lets the browser handle these for you. <dialog> has been widely available in major browsers (Baseline) since March 2022.
This article shows how to add CSS and JavaScript to a WordPress child theme and place the dialog in a "Custom HTML" block in the block editor (checked against WordPress 6.9 as of October 2026).
Basics of the dialog element
<dialog> can be opened in two ways.
| How it opens | Behavior |
|---|---|
dialog.showModal() |
Opens as a modal. Everything outside the dialog becomes inert, and the Esc key closes it. The background can be styled with ::backdrop |
dialog.show() |
Opens as a non-modal dialog. The background stays usable, and Esc does not close it |
We want a modal here, so we use showModal(). You can close it in these ways:
- Call
dialog.close()from JavaScript - Submit a
<form method="dialog">inside the dialog (nothing is sent to the server; the dialog closes) - Press Esc when it is open as a modal
When opened with showModal(), focus moves to the first focusable element in the dialog. To focus a specific element, give it the autofocus attribute.
What we’ll build
- A button opens the dialog
- A "Close" button, the Esc key, or a click on the background closes it
- The page itself doesn't scroll while the dialog is open
- You can place several dialogs on one page
The file layout is as follows (the child theme folder name is an example).
wp-content/themes/my-child-theme/
├── functions.php
└── assets/
├── modal.css
└── modal.js
If you edit the theme's files directly, a theme update overwrites them and your changes are lost. If you don't have a child theme yet, create one first. The steps are in "How to create a child theme in WordPress".
Steps
1. Write the CSS
Create assets/modal.css.
/* The dialog itself. Padding goes on the inner element (see step 3) */
.my-modal {
padding: 0;
border: none;
border-radius: 8px;
width: min(90vw, 560px);
max-height: 85vh;
box-shadow: 0 10px 30px rgb(0 0 0 / 0.3);
}
.my-modal__body {
padding: 24px;
}
/* The dark overlay behind the dialog */
.my-modal::backdrop {
background: rgb(0 0 0 / 0.5);
}
.my-modal__close {
margin-top: 16px;
cursor: pointer;
}
/* Stop the page from scrolling while the dialog is open */
body:has(dialog.my-modal[open]) {
overflow: hidden;
}
/* A light animation on open */
.my-modal[open] {
animation: my-modal-in 0.2s ease-out;
}
@keyframes my-modal-in {
from {
opacity: 0;
transform: translateY(8px);
}
to {
opacity: 1;
transform: none;
}
}
/* No animation for users who prefer reduced motion */
@media (prefers-reduced-motion: reduce) {
.my-modal[open] {
animation: none;
}
}
A <dialog> opened as a modal is centered on the screen by the browser's default styles. You don't need CSS to position it.
2. Write the JavaScript
Create assets/modal.js. It listens for clicks on the whole page in one place, so the same code works no matter how many dialogs and buttons you have.
document.addEventListener('click', function (event) {
// Open with a button that has data-modal-open="dialog-id"
const opener = event.target.closest('[data-modal-open]');
if (opener) {
const dialog = document.getElementById(opener.dataset.modalOpen);
if (dialog && typeof dialog.showModal === 'function') {
dialog.showModal();
}
return;
}
// A click on the backdrop (::backdrop) arrives as a click on the dialog element itself
if (event.target instanceof HTMLDialogElement && event.target.open) {
event.target.close();
}
});
3. How closing on a background click works
::backdrop is treated as part of the <dialog> element, so when you click the background, event.target is the <dialog> itself. When you click the dialog's content, event.target is an inner element (.my-modal__body or one of its children).
That is why the CSS in step 1 sets the <dialog>'s padding to 0 and puts the spacing on the inner .my-modal__body. If the <dialog> itself had padding, clicks on that padding would also count as "background clicks" and close the dialog.
If you don't want a background click to close it (for example, a form that would be annoying to lose by accident), delete the second if block. The Esc key and the "Close" button still close it.
4. Load the files from functions.php
Add this to the child theme's functions.php. Since WordPress 6.3 you can pass an array as the fifth argument of wp_enqueue_script() to set defer.
<?php
// Add to the child theme's functions.php
add_action( 'wp_enqueue_scripts', function () {
$dir = get_stylesheet_directory();
$uri = get_stylesheet_directory_uri();
wp_enqueue_style(
'my-modal',
$uri . '/assets/modal.css',
array(),
filemtime( $dir . '/assets/modal.css' )
);
wp_enqueue_script(
'my-modal',
$uri . '/assets/modal.js',
array(),
filemtime( $dir . '/assets/modal.js' ),
array(
'strategy' => 'defer',
'in_footer' => true,
)
);
} );
If functions.php already starts with <?php, add only the lines from the second line on. The version uses the file's modification time (filemtime()), so editing the CSS or JavaScript automatically busts the browser cache.
To load the files only on specific pages, add a condition at the top of the function.
<?php
add_action( 'wp_enqueue_scripts', function () {
// Don't load on anything except the "contact" page
if ( ! is_page( 'contact' ) ) {
return;
}
// ...put the wp_enqueue_style / wp_enqueue_script calls from step 4 here...
} );
For details on the arguments of wp_enqueue_scripts, see "Loading JS and CSS with wp_enqueue_scripts".
5. Place the dialog HTML
Edit the post or page where you want the dialog, add a "Custom HTML" block, and paste the following HTML.
<button type="button" class="wp-element-button" data-modal-open="info-modal">Learn more</button>
<dialog id="info-modal" class="my-modal" aria-labelledby="info-modal-title">
<div class="my-modal__body">
<h2 id="info-modal-title">Notice</h2>
<p>Write the content you want to show here.</p>
<form method="dialog">
<button type="submit" class="my-modal__close">Close</button>
</form>
</div>
</dialog>
- Make the button's
data-modal-openvalue match the<dialog>'sid. - If you place several on the same page, give each dialog a different
id(andaria-labelledby/ headingid). wp-element-buttonis the class that applies a block theme's button styles. You can remove it if you don't need it.- Pointing
aria-labelledbyat the heading lets screen readers announce it as the dialog's name. - Don't put
tabindexon the<dialog>element itself.
WordPress filters the HTML saved in posts according to the user's capabilities. On a single site, Administrators and Editors have the unfiltered_html capability, so the HTML is saved as is. If someone with another role, such as Author, saves it, some tags or attributes may be stripped.
6. Check that it works
On the published page, check the following:
- The button opens the dialog and the background darkens
- The "Close" button, the Esc key and a background click close it
- While it is open, the Tab key doesn't move focus outside the dialog
- Clicking the dialog's content (text or padding) doesn't close it
- It doesn't overflow the screen at smartphone widths
If it doesn't open, use your browser's developer tools (F12): check "Console" for errors and "Network" to see whether modal.js and modal.css load (and don't return 404). If you use a caching plugin, clear the cache before checking.
Things to keep in mind
- Don't show it automatically on page load: A dialog that covers the content as soon as the page loads tends to get in readers' way. The basic pattern is the one in this article: open it when the reader presses a button.
- Provide a visible way to close it: Some users can't use the Esc key, so put a "Close" button on screen.
- If you put a form inside: If a form in the dialog sends information, you still need server-side validation and escaping, just as with any form. For contact forms and the like, you can also build the form with a form plugin and place the block containing its shortcode inside the dialog.
- Compared with popup plugins: If you need control over when it appears (scroll depth, exit intent) or how often, a dedicated plugin may be quicker. For a comparison, see "How to implement popups in WordPress".
Summary
- Build modal dialogs with the HTML
<dialog>element andshowModal(), and the browser handles closing with Esc and blocking the background. - Put the CSS and JavaScript in a child theme and load them with
wp_enqueue_scriptsinfunctions.php. - Place the dialog HTML in a "Custom HTML" block and match the button's
data-modal-opento theid. - For closing on a background click, set the
<dialog>'spaddingto 0 and wrap the content in an inner element so clicks aren't misdetected.
Questions about WordPress? The publisher of this site is based in Japan and replies in Japanese. Contact (Japanese)