Skip to Content
Component ExamplesNon-Modal Dialogs

Overview

Non-modal dialogs differ from modal dialogs in one critical way: the background content remains fully interactive. Users can still tab to, click, and interact with the rest of the page while the dialog is open. The native <dialog> element supports this via .show() (as opposed to .showModal() which creates a modal). Non-modal dialogs are ideal for chat widgets, editing panels, floating toolbars, and any supplementary UI that should not block the user’s workflow.

WCAG Criteria:

  • 4.1.2 Name, Role, Value  — the dialog must have a role and an accessible name
  • 2.1.1 Keyboard  — all functionality must be operable via keyboard, including opening, interacting with, and closing the dialog

Key requirements:

  • Use the native <dialog> element with .show() for non-modal dialogs (not .showModal())
  • Label the dialog with aria-labelledby pointing to its heading
  • Move focus to the dialog when it opens
  • Close on Escape key and return focus to the trigger element
  • Do not trap focus — the user must be able to Tab out of the dialog into the rest of the page
  • Background content must remain fully interactive (no backdrop, no inert)

Non-Modal Panel

Custom Div Panel vs. Native Non-Modal Dialog

Inaccessible
<!-- Custom div panel — no role, no focus management, no Escape --> <button onclick="togglePanel()">Chat</button> <div id="panel" style="display:none"> <div>Chat Support</div> <p>Hi there! How can we help you today?</p> <button onclick="closePanel()">Close</button> </div>
Live Preview
Accessible
<!-- Native <dialog> with .show() — non-modal, no backdrop --> <button id="trigger" onclick=" var dlg = document.getElementById('dlg'); if (!dlg.open) { dlg.show(); dlg.querySelector('h2').focus(); } else { dlg.close(); } "> Chat </button> <dialog id="dlg" aria-labelledby="dlg-title" onclose="document.getElementById('trigger').focus()" onkeydown="if (event.key === 'Escape') this.close()" > <h2 id="dlg-title" tabindex="-1">Chat Support</h2> <p>Hi there! How can we help you today?</p> <button onclick="this.closest('dialog').close()">Close</button> </dialog>
Live Preview

Chat Support

Hi there! How can we help you today?

What’s wrong with the custom div panel?

  • No dialog role — screen readers announce it as generic content, not as a dialog
  • No aria-labelledby — screen readers cannot identify the purpose of the panel
  • No focus management — when the panel opens, focus stays on the trigger button and keyboard users must guess where the panel appeared
  • No Escape key handling — keyboard users cannot dismiss the panel without tabbing to the Close button
  • No focus restoration — when the panel closes, focus is not returned to the trigger

What the native <dialog> with .show() gives you:

  • Built-in dialog role in the accessibility tree
  • .show() opens it non-modally — no backdrop, no inert on background, no focus trap
  • aria-labelledby links the dialog to its heading for a clear accessible name
  • Focus moves to the heading on open so screen readers immediately announce the dialog context
  • tabindex="-1" on the heading allows it to receive programmatic focus without entering the tab order
  • Escape key closes the dialog and the onclose event restores focus to the trigger
  • Users can Tab freely out of the dialog into the rest of the page

What the screen reader announces:

VersionAnnouncement
Custom div”Chat Support” (just text — no dialog context)
Native <dialog> with .show()”Chat Support, heading level 2, dialog” (dialog role and heading announced)

.show() vs .showModal() — key differences:

Behavior.show() (non-modal).showModal() (modal)
Background interactiveYesNo (background is inert)
Focus trapNo — Tab can leave the dialogYes — focus is trapped inside
BackdropNone::backdrop pseudo-element
Escape closesYes (with manual handler)Yes (automatic)
Top layerNoYes

Resources