Modals and drawers
Modal / drawer primitive
For when the answer to “It has modals, drawers, or dialogs” is yes.
Focus-trap, Esc-close, role=dialog with an accessible name, focus returned to the opener, background scroll lock.
Built by hand again in 18 of 20 audited projects before this existed. Each rebuild was another chance to make the mistake below.
Skip the rebuild
You do not write this one. It arrives in your project as ordinary source you can read, change and keep, with its audit record and its caveats beside it.
- Download the Playground. It is free and runs on your own machine.
- Start a new app and tick “It has modals, drawers, or dialogs”.
- This block is written in, along with anything else you ticked.
The mistake it removes
The most-rebuilt block in the audit (18/20 projects) and almost always rebuilt without dialog semantics — the Coaching App's audit flagged exactly that.
What the audit found
Named rather than summarized. The reasoning behind each one ships inside the block, so it travels with the code instead of living on a page you have to trust.
- A dialog containing no focusable element is a keyboard dead-end minor · Accessibility
What you still have to do
A copied file cannot own your secrets, your host or your legal obligations. This is the part the block deliberately does not claim.
- Escape or build safely any Element you pass as `content`. The string path uses textContent and never innerHTML, so a plain string cannot execute; rich content is a DOM node the caller constructs, and what goes into it is the caller's to make safe.
- A keyboard-and-screen-reader pass on YOUR page. The block ships the wiring it can ship — role/aria-modal, an aria-label, a tabindex-based focus trap and focus restored on close — but color contrast, heading order and the focus order across everything around it are properties of the page you drop this into, and no test here can see them. Run one manual keyboard pass and one screen-reader pass before you launch. Since ph0tsk596 the block's browser leg drives the focus trap, Escape, focus restore and scroll lock of both stacks in headless Chromium (see its dd.json), which is not a screen reader and is not your page: a manual pass is still what finds focus escaping into the content around it.
What lands in your project
vanilla
- vanilla/overlay.js
openOverlay({label, content, variant}). Call installOverlayStyles() once, or paste the CSS into your own stylesheet.
react
- react/Overlay.jsx
<Overlay label=… onClose=…>. Styles inline from your design tokens; no CSS file to install.
Licensed MIT. It is a starting point, not a finished product.
Get it
Download the Playground See the other blocks
Nothing here is locked. The files are yours, in your folder, under a permissive license.
Questions
- Is Modals and drawers audited?
- Yes. A full due-diligence audit was run on 2026-07-17 and the verdict was a pass.
- What do I still have to do myself?
- Escape or build safely any Element you pass as `content`. The string path uses textContent and never innerHTML, so a plain string cannot execute; rich content is a DOM node the caller constructs, and what goes into it is the caller's to make safe.
- How do I get this code?
- Download the Playground, start a new app, and tick “It has modals, drawers, or dialogs”. The block is written into your project as ordinary source you can read and edit.
All pre-built code blocks · Learn to build from zero · The coding guide