Modal: dialog
A modal is a window over the page that dims everything else and waits for an answer: confirm an action, fill in a short form. HTML has the <dialog> tag for it, and the browser can open and close it by itself. Pico adds the dimmed backdrop and styles the content as a card.
The modal markup
Inside <dialog> goes an <article> — the card from earlier lessons. A button with rel="prev" in the card header turns into a close cross:
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css">
<div style="min-height: 280px"></div>
<dialog open>
<article>
<header>
<form method="dialog">
<button aria-label="Close" rel="prev"></button>
</form>
<p><strong>Thanks for subscribing!</strong></p>
</header>
<p>New blog posts will arrive on Fridays.</p>
</article>
</dialog>The modal is open right away thanks to the open attribute: the background is dimmed, and a card with a heading and a cross sits in the center. Click the cross and the modal closes. A button inside <form method="dialog"> closes its <dialog> without a single line of JavaScript. The empty min-height block gives the modal room in the preview.
Opening with a button
Usually the modal is hidden and a script opens it with the showModal() method. The close() method closes it, and so does the Escape key:
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css">
<div style="min-height: 300px">
<button id="open-contact">Contact me</button>
</div>
<dialog id="contact">
<article>
<header>
<button aria-label="Close" rel="prev" data-close></button>
<p><strong>Contact Anna</strong></p>
</header>
<label for="reply">Your email</label>
<input type="email" id="reply">
<footer>
<button class="secondary" data-close>Cancel</button>
<button data-close>Send</button>
</footer>
</article>
</dialog>
<script>
const dialog = document.querySelector('#contact');
document.querySelector('#open-contact').addEventListener('click', () => dialog.showModal());
dialog.querySelectorAll('[data-close]').forEach((button) => {
button.addEventListener('click', () => dialog.close());
});
dialog.addEventListener('close', () => console.log('Modal closed'));
</script>Click "Contact me" — a modal opens with an email field and two buttons in the footer. Any of the three buttons or Escape closes it, and "Modal closed" appears under the example: the close event fires however the modal is closed.
What does what
| Part | Job |
|---|---|
<dialog> | The full-screen dimmed layer |
<article> | The modal card itself |
<header> with rel="prev" | The title and the cross |
<footer> | Action buttons aligned to the right |
showModal() and close() | Open and close from a script |
showModal() also keeps focus inside the modal: the Tab key does not wander off to links on the page under the backdrop.
Common mistake
Opening the modal by simply adding the open attribute from a script. The modal appears, but the browser does not treat it as modal: Escape does not close it, and Tab moves focus to the page underneath. For windows that wait for an answer, use showModal().