sodcalculator

10 Tech How-To

HTML Dialog Element: How to Use and Test Native Dialogs

Guide 10-28196 · Updated · Step by step

The HTML <dialog> element lets you create a modal or non-modal dialog box using only native browser features, without needing a JavaScript library. You open it with show() or showModal(), close it with close(), and test it with standard DOM and accessibility tools. This article explains how to implement, style, and test native dialogs, covering common pitfalls and best practices.

What Is the Native Dialog Element?

Introduced in HTML 5.2 and now supported in all modern browsers, the <dialog> element provides a built-in container for interactive overlays. It comes with default styling, focus management, and a backdrop pseudo-element for modals. Unlike custom-built popups, a native dialog is lightweight, accessible, and requires no extra JavaScript for basic behavior.

The element has two display modes: non-modal (using show()) and modal (using showModal()). Non-modal dialogs do not block interaction with the rest of the page and do not show a backdrop. Modal dialogs do block interaction, show a backdrop, and trap focus inside the dialog until it is closed.

How to Use the Dialog Element

To create a dialog, you simply add the element to your HTML and control it with JavaScript. Here is a minimal example:

<dialog id="myDialog">
  <p>This is a native dialog.</p>
  <button id="closeBtn">Close</button>
</dialog>

<button id="openBtn">Open Dialog</button>

<script>
  const dialog = document.getElementById('myDialog');
  const openBtn = document.getElementById('openBtn');
  const closeBtn = document.getElementById('closeBtn');

  openBtn.addEventListener('click', () => dialog.showModal());
  closeBtn.addEventListener('click', () => dialog.close());
</script>

When you call showModal(), the dialog appears on top of the page, and the browser automatically focuses the first focusable element inside it. To close, you can call close() or have the user press the Escape key—the browser handles that for you by default.

If you want a non-modal dialog, use show() instead. Non-modal dialogs are useful for tooltips or inline panels that do not need to block interaction.

Styling and Customizing the Dialog

The <dialog> element comes with default styles, but you can override them with CSS. The most important pseudo-element is ::backdrop, which styles the area behind a modal dialog. For example:

dialog::backdrop {
  background: rgba(0, 0, 0, 0.5);
}

You can also set the dialog’s width, border, and padding. Note that the default dialog has a border and a background color, but you can reset them:

dialog {
  border: none;
  border-radius: 8px;
  padding: 20px;
  width: 80%;
  max-width: 500px;
}

One common mistake is trying to hide a dialog with display: none. That works, but it prevents the dialog from being displayed when you call show() or showModal(). Instead, use the open attribute or the close() method to control visibility. When you close a dialog, the browser automatically removes the open attribute and hides it.

Testing Native Dialogs

Testing a native dialog is straightforward with browser DevTools and automated testing frameworks. Here are the key aspects to test:

  • Opening and closing: Verify that clicking the trigger opens the dialog and that the close button or Escape key closes it.
  • Focus management: When a modal dialog opens, focus should move inside it. When it closes, focus should return to the element that opened it (the browser does this automatically). Test with keyboard navigation.
  • Accessibility: The dialog should have a proper aria-labelledby or aria-label to describe its purpose. Test with a screen reader.
  • Backdrop interaction: Clicking the backdrop does not close the dialog by default. If you want that behavior, you need to add a click listener on the dialog element itself (checking if the click target is the dialog, not its children).

For automated testing, tools like Playwright and Cypress can interact with dialogs using standard selectors. For example, in Playwright you can do:

await page.click('#openBtn');
await page.waitForSelector('#myDialog[open]');
await page.click('#closeBtn');
await page.waitForSelector('#myDialog:not([open])');

In unit tests with jsdom, you may need to polyfill the dialog element because jsdom does not fully support it. The dialog-polyfill package is commonly used for that purpose.

Common Problems and Solutions

Here are frequent issues developers encounter with native dialogs and how to fix them:

  • Dialog does not appear: Make sure you are calling show() or showModal() on the element, not just setting the open attribute. The open attribute only works for static HTML, not for dynamic opening via JavaScript.
  • Escape key does not close: For modal dialogs, Escape works by default. If it does not, check if you have a keydown listener that prevents the default behavior.
  • Focus is not trapped: In modal dialogs, the browser should trap focus. If it does not, you might be using an older browser or a polyfill that does not implement focus trapping correctly.
  • Backdrop click closes the dialog: This is not default behavior. If you want it, add a click listener on the dialog and check event.target === dialog to close it.
  • Form submission inside dialog: If you have a <form method="dialog">, submitting it will automatically close the dialog and pass the submit button’s value to the close() method. This is a handy feature but can be confusing if you expect a regular form submission.

Accessibility and Best Practices

The native dialog element is more accessible than custom popups, but you still need to follow best practices:

  • Use showModal() for dialogs that require user attention. It sets aria-modal="true" and prevents screen readers from accessing background content.
  • Provide a label using aria-labelledby (pointing to a heading) or aria-label.
  • Ensure all interactive elements inside the dialog are focusable and that the tab order is logical.
  • When the dialog closes, focus returns to the previously focused element automatically. Do not manually move focus unless necessary.
  • For non-modal dialogs, consider adding aria-expanded on the trigger button to indicate the dialog’s state.

A common misconception is that the <dialog> element is not accessible because it is new. In reality, it is built on ARIA patterns and is well-supported by screen readers when used correctly.

Alternatives and Browser Support

While the native dialog element is supported in all modern browsers (Chrome, Firefox, Safari, Edge), older browsers like Internet Explorer and some older mobile browsers do not support it. For those, you can use a polyfill like dialog-polyfill, which mimics the behavior using divs and ARIA attributes.

If you need more complex dialog features—such as draggable, resizable, or stacked dialogs—you might consider a library like a11y-dialog or a full UI framework like Bootstrap’s modal. However, for simple use cases, the native element is lighter and faster.

Another alternative is the <popover> attribute (part of the Popover API), which is newer and still experimental in some browsers. It is more suitable for non-modal overlays like tooltips and dropdowns.

Frequently Asked Questions

Can I use the <dialog> element for a confirmation prompt?

Yes, you can. Create a dialog with a message and two buttons (Confirm and Cancel). Use the close() method with a return value, and listen to the close event to read that value. For example, dialog.close('confirm') or dialog.close('cancel').

How do I prevent the dialog from closing when clicking outside?

By default, clicking outside a modal dialog does not close it. If you want to close on backdrop click, add a click listener on the dialog element and check event.target === dialog. For non-modal dialogs, the same applies.

Does the dialog element work with forms?

Yes. A form with method="dialog" will close the dialog when submitted. The submit button’s value is passed to the close() method. This is useful for simple forms where you do not need to send data to a server.

How do I make the dialog non-focusable?

You cannot make a dialog completely non-focusable because it needs to receive focus for accessibility. However, you can set tabindex="-1" on the dialog element to prevent it from being tabbed to, but its children will still be focusable.

What is the difference between show() and showModal()?

show() opens the dialog as non-modal, meaning it does not block interaction with the rest of the page and does not show a backdrop. showModal() opens it as modal, blocking interaction, showing a backdrop, and trapping focus inside the dialog.

Can I animate the dialog’s appearance?

Yes, you can use CSS transitions or animations on the dialog element. For example, you can animate opacity and transform when it opens. Be aware that the display property changes from none to block, which may affect transitions. You can use the @starting-style rule or a small JavaScript delay to handle this.

Related

General steps for current mainstream versions; menus can differ by device and version. See the disclaimer or report an error.

More in this bed

The whole Tech How-To bed →