- Design system
- Overlays
06 — Overlays
Overlays
Modals, drawers, menus, tooltips, toasts and accordions. All of them are built on <dialog> or <details>, so focus trapping, Esc-to-close and the top layer come from the platform.
dialog.showModal() gives you all five, in every current browser.
Modal
A native <dialog>. Open it with .showModal(), never by toggling a class — the method is what puts it in the top layer and traps focus. A modal interrupts, so use one only when the task genuinely cannot continue behind it.
<button class="btn btn--filled" data-open="signin">Sign in</button>
<dialog class="modal" id="signin" aria-labelledby="signin-t">
<div class="modal__head">
<div>
<h2 class="modal__title" id="signin-t">Sign in</h2>
<p class="modal__desc">Use the address the invite was sent to.</p>
</div>
<button class="iconbtn" data-close aria-label="Close">…</button>
</div>
<div class="modal__body">…</div>
<div class="modal__foot">
<button class="btn btn--quiet" data-close>Cancel</button>
<button class="btn btn--filled">Sign in</button>
</div>
</dialog>
<script>document.getElementById('signin').showModal();</script>
Point aria-labelledby at the title. Give the close button an aria-label — an icon alone has no accessible name.
Drawer
The same <dialog>, pinned to an edge and full height. Use a drawer when the content is a list or a filter set the user will scan against what is behind it; use a modal when they must answer before continuing.
Dropdown menu
A <details> with a positioned list. It opens, closes, toggles on Enter and Space and works with JavaScript off — none of which you get from a div.
<details class="menu">
<summary class="btn btn--quiet">Actions</summary>
<div class="menu__list">
<a href="/open">Open in new tab</a>
<div class="menu__sep"></div>
<button type="button">Delete</button>
</div>
</details>
Use .menu__list--end to align the panel to the right edge — necessary for anything near the end of a header, or the menu opens off-screen.
Nav sheet
The mobile menu — the one behind the button in the top right of this page below 900 px. Also a <details>; nav.js only adds Esc, click-outside and the body scroll lock.
<header class="site-head">
…
<button class="navsheet__trigger" data-navsheet-open aria-label="Menu">
<svg …></svg>
</button>
</header>
<!-- A SIBLING of the header. See the warning below. -->
<dialog class="drawer navsheet" data-navsheet aria-label="Site menu">
<div class="navsheet__panel">
<div class="navsheet__head">
<span class="navsheet__title">Site name</span>
<button class="iconbtn" data-close aria-label="Close menu">…</button>
</div>
<a class="sheet-row" href="/work">Work</a>
<a class="sheet-row" href="/about">About</a>
<div class="sheet-tools">…</div>
</div>
</dialog>
The sheet must be a sibling of the header, not a child. .site-head sets backdrop-filter, and any filter makes an element a containing block for its position:fixed descendants — a sheet nested inside it gets trapped in the 56 px header box in the corner instead of covering the viewport. Opening it with showModal() puts it in the top layer, which is outside that containing block entirely.
Tooltip
Decorative only. A tooltip is unreachable on touch and unreliable across screen readers — anything the user actually needs belongs in visible copy or a .hint.
The button carries the real name via aria-label; the bubble is role="presentation" so it is not announced twice.
Toast
A transient confirmation. Use one for something that succeeded and needs no response. Never put an action inside a toast that only exists there — it disappears on a timer.
<div class="toasts" data-toasts aria-live="polite"></div> <!-- one toast, appended by script --> <div class="toast" role="status"> <span class="toast__icon" aria-hidden="true">●</span> <span>Settings saved</span> </div>
The host is aria-live="polite" and each toast is role="status", so it is announced without interrupting. Errors that need a decision belong in a .note on the page, not a toast.
Accordion
Grouped <details>. Good for an FAQ. Not good for content someone needs to compare across — collapsing that just makes them click more.
What does a typical engagement look like?
Do you work with existing teams?
Which regions?
Add name="faq" to each <details> to make the group exclusive — opening one closes the others, with no JavaScript.
<dialog> does this for you; a hand-rolled one will not.