Dialog · Scrim
A card that interrupts — one question, one decision, on a scrim over the page.
Beta · Navigation and overlay · packages/css/src/components/dialog.css · React on Base UI: @pepperui/dialog
.pui-scrim (fixed; --static here) · .pui-dialog (--medium --large; __header __texts __title __description __close __content __footer).
Sizes across, states down.
What it is for
Section titled “What it is for”- Three widths: small 400 (confirms), medium 480 (forms and choices), large 680 (editor modals)
- Title, Description, Has description, Has close, Has content; the two Buttons and the Close are exposed — set their labels and states on the instance
- Content is a slot: replace it with the body (a Field, a Textarea, a list). Keep the footer’s primary as the verb: Publish, Delete, Send
- Sits on Scrim; elevation/modal; radius 16
Use when the user must answer before going on — confirm, name, choose, send.
Do not use when the content is a whole task (a page or a Sheet), the message needs no answer (Alert, Toast), or the surface anchors to a trigger (Popover).
Relates to Scrim, Alert dialog, Sheet, Drawer, Button, Icon button - ghost, Field, Textarea.
Anti-patterns a dialog over a dialog · a dialog without a way out · three primaries in the footer · a title that is a sentence with a full stop · scrolling inside a small dialog.
Keywords: dialog, modal, confirm, popup, overlay
Scrim is the overlay; in code it is Dialog’s backdrop, not a component
Figma — page Dialog: Dialog (3 variants) · Scrim (1 variants)
shadcn: dialog
The markup — copy this
Section titled “The markup — copy this”The class, the parts, and nothing that is not in the package. The same block opens dialog.css.
<div class="pui-scrim" data-state="open"> <div class="pui-dialog pui-dialog--medium" role="dialog" aria-modal="true" aria-labelledby="t"> <div class="pui-dialog__header"><div class="pui-dialog__texts"><h2 class="pui-dialog__title" id="t">Rename deck</h2> <p class="pui-dialog__description">…</p></div> <button class="pui-icon-button pui-icon-button--ghost pui-icon-button--small pui-dialog__close" aria-label="Close">…x…</button></div> <div class="pui-dialog__content"> …a Field… </div> <div class="pui-dialog__footer"><button class="pui-button pui-button--secondary">Cancel</button><button class="pui-button">Save</button></div></div></div>The modifiers
Section titled “The modifiers”| What it is | Note | |
|---|---|---|
| Size | (none) small 400 · --medium 480 · --large 680 | — |
| Scrim | .pui-scrim — fixed, the viewport, centred content; [hidden] when closed | — |
Accessibility — measured, not assumed
Section titled “Accessibility — measured, not assumed”Every number here is measured on the rendered component: contrast (npm run contrast:render), the focus ring (npm run focus) and the keyboard (npm run keyboard). The floors are principle 12’s.
Contrast
Section titled “Contrast”| ink on ground | reads | floor | the matrix calls it | |
|---|---|---|---|---|
#544d4d on #f2f2f7 |
7.39 | 4.5 | text.muted on surface.card (promised) | ok |
#0b0202 on #f2f2f7 |
18.38 | 4.5 | text.default on surface.card (promised) | ok |
#0b0202 on #ffffff |
20.51 | 4.5 | text.default on surface.page (promised) | ok |
#ffffff on #0b0202 |
20.51 | 4.5 | Primary button label (promised) | ok |
4 distinct pair(s) rendered, 0 that axe calls a violation.
The focus ring
Section titled “The focus ring”4 of 4 focus stop(s) in this block meet WCAG 2.4.13 on screen — the same crop screenshotted focused and unfocused, and the difference measured.
Keyboard
Section titled “Keyboard”| role | tab stops | what the pattern requires | |
|---|---|---|---|
dialog .pui-dialog.pui-dialog--medium |
4 | — | — |
What the consumer’s script owes. focus moves into the dialog on open and stays inside while it is open — legal under WCAG 2.1.2 ONLY because Escape gets out; Escape closes and focus returns to whatever opened it.
Tab order across the whole library is document order over 344 stops, and Shift+Tab is its exact reverse — no keyboard trap anywhere (WCAG 2.1.2).
Pepper UI v1.5.0
The Mobupps design system. 386 tokens, 88 components, 65 pages in the workshop — every page of this site generated from the repository, so nothing here is typed twice.