HTML dialog educational guide
Ask for attention with a native dialog
A confirmation needs a decision; an inspector can coexist with the page. Learn to choose between modal and non-modal, open and close dialog, place focus, and collect a response without building your own interaction layer.
Mental model
dialog provides the semantics of an interaction window. The opening method decides whether it blocks the rest of the document.
showModal() places the dialog in the top layer and makes the rest of its document inert: outside content cannot be clicked or focused while it remains open. The browser manages this modality; you do not need a huge z-index or your own Tab loop.
show() opens a non-modal window: it receives initial focus, but the page remains available and Tab can leave the dialog. It does not enter the top layer or generate a modal backdrop. CSS can adapt its position, as with this guide’s panel in the document flow.
The open attribute indicates that dialog is open, but does not say it is modal. Writing <dialog open> shows it as non-modal and does not run the focusing steps of show(). Use the API or commands to open it; use close() or a dialog form to close it.
Open according to the task
A brief confirmation may interrupt the flow. A reference panel usually needs users to keep working outside it.
With command="show-modal" and commandfor, the button targets the dialog id and requests modal opening without an opening listener. The recipe adds JavaScript only for the fallback in browsers without commands and to display returnValue. It does not call showModal() a second time when native commands are supported.
Modal confirmation with a response
Last response:
Reference panel without blocking the page
Each recipe’s HTML, CSS, and JavaScript form a complete example. IDs belong to the guide and do not change the playground. Without JS, the confirmation works if the browser supports commands, although the result does not update; non-modal opening stays disabled.
Do not turn a dialog already opened with show() into a modal by calling showModal(): it can throw InvalidStateError. Close it first and reopen in the chosen mode. The dialog.open guard in these recipes avoids repeating the opening of the same example.
Focus and pattern choice
Modality defines the journey, but content defines where to start. An accessible name and a visible exit remain necessary.
aria-labelledby connects the dialog to its visible title. aria-describedby works here because the explanation is brief; do not apply it to a long body with lists or tables that needs structural exploration. dialog already provides its role; showModal() provides accessible modality.
Use autofocus on the control that makes sense on entry. This confirmation starts at Cancel, the safe choice; a form might start at its first field. For long text, an inner heading with tabindex="-1" may be a useful focus destination. Do not add tabindex to dialog itself or force focus on every render.
On closing, the browser can return focus to the element focused before opening. A non-modal should not steal it if the user has already moved outside. Check the actual journey; if the trigger disappears after the action, move focus to a logical destination on the new screen.
dialog: a task in a window
Choose modality for a decision that needs completion or cancellation before continuing. Use show() for an interaction window that can coexist with the page. Reserve interruption for a specific need.
Popover: supplementary content
Contextual help, actions, or temporary content that should not block the document. Popover offers the top layer and dismissal modes, but remains non-modal: being above the page does not make everything else inert.
details: expandable content
An explanation expanded in the flow through summary, without a dialog window or modality. It fits content that belongs to that section and only needs to be shown or hidden.
Close and request closure
Always offer an explicit control. Escape, an outside tap, and a form response are distinct paths, not a single confirmation signal.
By default, a modal accepts close requests such as Escape and does not close on an outside click. A non-modal does not accept Escape by default. Do not make the keyboard the only exit: keep Cancel or Close for mouse, touch, and assistive technology.
closedby="any"
Add closedby="any" for outside dismissal, platform requests, and explicit mechanisms. Treat it as progressive enhancement: without closedby support, the outside area will not close the modal, but the button and Escape remain exits.
closedby="closerequest"
closedby="closerequest" accepts platform requests and explicit mechanisms, without outside dismissal. This is the default modal behavior when the attribute is missing or invalid; it does not promise the same gesture on every platform.
closedby="none"
closedby="none" allows explicit mechanisms and disables outside dismissal and user close requests where supported. Do not use it to force confirmation. A browser that ignores the attribute keeps its default behavior.
cancel fires for a close request, such as Escape. Listen directly on dialog; preventDefault() can stop it when protecting pending work requires this, but a clear exit must remain. It does not fire simply from calling close() or submitting method="dialog".
close reports that the dialog has closed; it is not cancelable and does not bubble. Use it to collect the result or clean up resources. close(value) closes directly; requestClose(value) requests closure through cancel, so it can be rejected. Detect requestClose separately before using it.
Forms and results
Closing a window and saving data are distinct operations. The confirmation above only communicates a local choice.
method="dialog" closes the dialog on form submission and puts the submit button’s value into returnValue. It makes no network request and does not clear fields. formmethod="dialog" can reserve this behavior for one button in a form that uses another method.
Native validation still applies to submission: an empty required field can prevent closing. This is why Cancel has formnovalidate; discarding should not require completing a form. A button that only opens or calls close() should have type="button" to avoid accidental submission.
Read returnValue on close, but do not assume every closure confirms. Escape or close() without an argument can retain an earlier value. The recipe resets it before opening. For a real action, process only confirm as explicit confirmation; cancel and the empty string are exits without confirmation.
If you need to save data, submit it and handle validation, waiting, and errors before closing. method="dialog" does not replace that process. The guide performs no real operation: the result lets you observe the difference between a choice and dismissal.
Style without losing behavior
Appearance can fit the site without replacing native behavior. Keep room for text, actions, and focus.
The browser hides dialog when it has no open attribute. Avoid an unconditional dialog { display: grid; } rule, which could show it while closed. If you need to change display, restrict the rule to dialog[open]. The recipes’ inner form can use Grid without affecting visibility.
dialog::backdrop paints the layer behind the modal in the top layer. It is not a DOM node, does not enable outside dismissal, and does not appear when opening with show(). Its color only helps identify the blocked context.
Limit width to the viewport and allow internal scrolling for short windows and enlarged text. The panel and buttons should wrap and keep visible focus. The recipe uses logical properties and a height limit; it adds no animations, so interaction also works with reduced motion.
Common mistakes
Identify the symptom and fix the cause before adding another overlay.
Visible, but the page remains operable
- What you expected
- Show a modal that blocks the document.
- Why it happens
- open or show() open a non-modal dialog; z-index and aria-modal do not change this.
- How to fix it
- Open with showModal() or command="show-modal" and check focus and Tab.
The declarative button does not open it
- What you expected
- Open the dialog from HTML in any browser.
- Why it happens
- Commands are unsupported or commandfor does not match a unique id.
- How to fix it
- Check the association and detect commandForElement. Use showModal() as a local fallback, without running both paths together.
Cancel requires completing fields
- What you expected
- Discard an edit while fields are still invalid.
- Why it happens
- A submit button inside method="dialog" also triggers validation.
- How to fix it
- Add formnovalidate to Cancel or use a type="button" button that calls close().
Escape seems to repeat confirmation
- What you expected
- Distinguish a new confirmation from closing without a choice.
- Why it happens
- returnValue can retain the value from the previous opening.
- How to fix it
- Reset it before opening and process only the explicit confirmation action value.
The dialog appears even when closed
- What you expected
- Hide it when the task finishes.
- Why it happens
- A display rule on dialog overrides the browser’s hiding style.
- How to fix it
- Apply display only with [open] and close through the API, not by removing open manually.
Support and progressive enhancement
The element, commands, and dismissal options have different statuses. A working modal does not verify every extension.
dialog, show(), showModal(), and method="dialog" form the Widely Available base. This base provides a native window without recreating modality or semantics. The lab separates declarative HTML from JavaScript to explore both paths.
command and commandfor are Newly Available, Baseline since December 12, 2025. The recipe detects commandForElement on HTMLButtonElement.prototype and uses showModal() when it is missing; form-based closing stays native.
closedby has limited availability: the consulted sources record support in Chromium and Firefox, but not Safari. The guide explains its values as an optional improvement and keeps explicit closure, without promising light dismiss where it does not exist.
Check typeof dialog.showModal === "function" for modal opening and "closedBy" in HTMLDialogElement.prototype for that extension. An attribute’s presence in HTML does not prove it works; the Baseline card also does not certify this browser or an accessibility test.
For an essential task without dialog support, offer the form or confirmation in a conventional page or section. Showing a block with CSS does not reproduce the top layer, inertness, or focus restoration. This guide’s editorial content and code blocks are served in full without JavaScript.