A modal dialog built on the native dialog element: it traps the focus, makes the rest of the page inert, closes with Esc and returns the focus to its opener, with a few lines of JavaScript.
- A button with
data-modal-open="{id}"opens the modal. .modalis thedialogelement, named by its title (aria-labelledby) and described by its subtitle (aria-describedby)..modal--lgmakes it wider; any width works with--inline-size..modal__panelis the visible box inside the dialog. Here it's aform method="dialog": submitting it closes the dialog and sets itsreturnValueto the submit button'svalue..modal__header,.modal__bodyand.modal__footer(with--space-between) structure the content. Every[data-modal-close]element closes it.
showModal()gives the hard parts for free: the focus moves into the dialog (to theautofocusfield, or the first focusable element), the page behind becomes inert, and Esc closes it.- When it closes, the browser returns the focus to the element that opened it.
- The close button has a visually hidden "Close" label; a click on the backdrop closes it too.
- Use a modal for focused tasks that block the page (confirmations, short forms). For long content or navigation, use the Drawer or a page.
- No dependencies. The script only wires the openers, the backdrop click and the close buttons; the
closeevent andreturnValuetell you how it was closed (here, the form is reset and amodal:submitevent fires). - The open and close animations use
@starting-styleandtransition-behavior: allow-discrete; browsers without them show the modal without the animation, and nothing animates with reduced motion. - The
dialogcovers the viewport and is the scroll container, so a panel taller than the screen scrolls with the whole viewport instead of inside the box. A click on the area around the panel targets the dialog itself, so it closes the modal like a backdrop click. - The page behind doesn't scroll while a modal is open (
html:has(.modal[open])). - The colors, the radius and the shadow come from the tokens; the old
modalcolor group isn't needed any more.
const modal = document.getElementById('invite-modal');
modal.addEventListener('modal:submit', () => {
// Send the form data to your back end.
});

