Native interactive elements handle focus, Escape and stacking for you, which custom modals routinely get wrong.
HTML now covers interactions that used to need a JavaScript library.
details and summary make a disclosure widget: <summary> is the always-visible toggle, the rest shows when open. No script required, keyboard accessible, searchable with find-in-page. Several <details> with the same name attribute form an exclusive accordion: opening one closes the others.
dialog is a native modal or non-modal box.
dialog.showModal()opens it as a modal: it's placed in the top layer above everything, the rest of the page becomes inert, focus moves inside, and Escape closes it. Style the dimmed background with::backdrop.dialog.show()opens it non-modally;dialog.close(value)closes it.- A
<form method="dialog">inside closes the dialog on submit and setsdialog.returnValueto the submitting button's value.
popover is an attribute that turns any element into a popup in the top layer: <div popover id="menu">. A button with popovertarget="menu" toggles it, with no JavaScript. popover (auto) popovers light-dismiss: clicking outside or pressing Escape closes them, and opening one closes other auto popovers. popover="manual" stays open until closed explicitly, for toasts.
Prefer dialog for anything that demands a response, and popover for menus, tooltips and pickers. Both avoid z-index battles because the top layer always wins.
Example
<details name="faq" open>
<summary>What is Topolearn?</summary>
<p>A tree of short lessons.</p>
</details>
<details name="faq">
<summary>Is it free?</summary>
<p>Yes, the essentials are.</p>
</details>
<button popovertarget="menu">Options</button>
<div popover id="menu">Rename · Duplicate · Delete</div>
<button id="del">Delete project</button>
<dialog id="confirm">
<form method="dialog">
<p>Delete this project?</p>
<button value="cancel">Cancel</button>
<button value="delete">Delete</button>
</form>
</dialog>
<script>
const dlg = document.getElementById("confirm");
document.getElementById("del").onclick = () => dlg.showModal();
dlg.onclose = () => console.log(dlg.returnValue); // "cancel" or "delete"
</script>Remember
- details/summary = disclosure with no JS; shared name = accordion
- showModal() gives top layer, inert background, Escape to close
- form method="dialog" closes and sets returnValue
- popover + popovertarget = light-dismiss popups with no JS
Watch out for
- Opening a modal with dialog.show() and wondering why the page behind stays interactive.