ESC

Type to search the knowledge base.

SnippetJavaScript

Modal

Accessible modal via native <dialog>: focus restore, Escape, backdrop dismiss.

Explanation

Prefer the native `<dialog>` when you can — it provides top-layer stacking and a cancel event. This helper covers open/close, Escape, backdrop click, and restoring focus. For full Tab-cycle focus trapping, layer a small trap helper or use a battle-tested dialog library.

javascript
export function createModal(dialog) {
  if (!(dialog instanceof HTMLDialogElement)) {
    throw new Error('Expected <dialog> element');
  }
  let lastFocus = null;

  function open() {
    lastFocus = document.activeElement;
    dialog.showModal();
    const focusable = dialog.querySelector(
      'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
    );
    focusable?.focus();
  }

  function close() {
    dialog.close();
    lastFocus?.focus?.();
  }

  dialog.addEventListener('click', (e) => {
    const rect = dialog.getBoundingClientRect();
    const inDialog =
      rect.top <= e.clientY && e.clientY <= rect.bottom &&
      rect.left <= e.clientX && e.clientX <= rect.right;
    if (!inDialog) close();
  });

  dialog.addEventListener('cancel', (e) => {
    e.preventDefault();
    close();
  });

  return { open, close };
}

Usage example

html
<dialog id="confirm">
  <form method="dialog">
    <p>Delete item?</p>
    <button value="cancel">Cancel</button>
    <button value="ok">Delete</button>
  </form>
</dialog>

const modal = createModal(document.getElementById('confirm'));
openBtn.addEventListener('click', () => modal.open());

Interview tips

  • Mention Escape, focus restore, and why native <dialog> helps (top layer, cancel event).
  • Call out full focus trap (Tab cycling) as a follow-up if not implemented.

Related

← All snippets