frontend-component-modal-toast-system
Installation
SKILL.md
Frontend Component : Modal + Toast System
This skill is the operational reference for building accessible modals and toast notifications using native browser primitives only : the <dialog> element with showModal() for modals, and the Popover API plus an aria-live region for toasts. It covers focus restoration, scroll lock, the live-region urgency mapping, toast queue management, and the @starting-style animation pattern. The skill does NOT cover the popover / dialog API mechanics deep-dive (see [[frontend-impl-popover-dialog-anchor]]), the ARIA roles deep-dive (see [[frontend-a11y-aria-patterns]]), or custom focus-trap libraries (use showModal() instead).
Quick Reference
Floor rules
- ALWAYS use
<dialog>withdialog.showModal()for modals. NEVER roll a custom focus-trap JS ;showModal()traps focus, inerts the rest of the document, enables Escape-close, and places the dialog on the top layer for free. - ALWAYS capture the triggering element before opening a modal AND restore focus to it on the
closeevent. The browser does NOT restore focus automatically. - ALWAYS use
aria-live="polite"for status, success, and informational toasts. NEVER usearia-live="assertive"for non-urgent updates ; it interrupts the screen reader mid-sentence. - ALWAYS use
popover="manual"for stacked toasts. NEVER usepopover="auto"; auto popovers are mutually exclusive in the top-layer stack so a second toast silently replaces the first. - ALWAYS create the
aria-liveregion in the DOM BEFORE inserting the message. NEVER create the region and the message in the same DOM mutation ; the screen reader observes mutations on a pre-existing region. - ALWAYS pair
<dialog>witharia-labelledby(pointing to the title) ORaria-label. NEVER ship a modal without an accessible name. - ALWAYS use
dialog.close(value)anddialog.returnValuefor confirm-style dialogs. NEVER hand-roll a Promise wrapper unless you also need to bridge to async / await. - NEVER set
tabindexon a<dialog>element. MDN forbids it ; the dialog manages its own tabindex.