01
Dialog
One bounded question on a raised sheet over a dimmed desk. The page behind is inert; focus goes in and comes back; Esc returns counsel to the work.
Anatomy
<dialog class="preceda-dialog">- Native modal: top layer, inert page, `cancel` on Esc. Raised paper, rule-strong border, dialog shadow.
__header / __kicker / __title- Optional small-caps kicker, the serif heading-role title, a 40px close control.
__description- One sentence of context in the body role; wired to aria-describedby.
__body- Scrolls inside the dialog; the header and foot stay put.
__actions / __footnote- A ruled foot, primary act last; a tertiary line of consequence.
--narrow / --wide- 480 px for confirms, 768 px for reference sheets such as shortcuts.
Variants
Live — share
Tab stays inside; Esc or the close control returns focus to “Share draft”; the copy toast renders inside the dialog.
Paper
Lamp
Live — keyboard shortcuts
Wide, with Kbd rows. Replaces the hand-rolled shortcuts modal.
Paper
Lamp
Default (drawn inline)
Paper
Lamp
Narrow
Paper
Lamp
State matrix
At 390 px
Below 640 px the dialog rises from the foot at full width with square lower corners, clears the home indicator, and stacks its actions full width with the primary act nearest the thumb (44 px).
390 px
Accessibility
- showModal(): the rest of the page is inert and hidden from assistive technology.
- Focus moves to [autofocus], `initialFocus`, or the first control; it returns to the opener (or `returnFocus`) on close.
- Tab and Shift+Tab wrap inside the dialog; Esc fires onClose("escape") unless `dismissible={false}`.
- aria-labelledby is the title; aria-describedby is the description.
Do
- Ask one question, with the context to answer it.
- Name the primary act with a verb.
- Keep the dialog controlled; the product owns open state.
Don’t
- Open a dialog for a notification.
- Stack dialogs.
- Hand-roll a fixed overlay — use the native dialog.
Usage
<dialog class="preceda-dialog" aria-labelledby="d-title" aria-describedby="d-desc">
<div class="preceda-dialog__sheet">
<header class="preceda-dialog__header">
<h2 class="preceda-dialog__title" id="d-title">Which court passed the impugned order?</h2>
<button class="preceda-dialog__close" type="button" aria-label="Close">…</button>
</header>
<p class="preceda-dialog__description" id="d-desc">This completes the jurisdiction paragraph.</p>
<div class="preceda-dialog__body">…</div>
<div class="preceda-dialog__actions">…</div>
</div>
</dialog><Dialog open={open} onClose={() => setOpen(false)}
kicker="Secure read-only link" title={`Share ${title}`}
description="Recipients see a frozen snapshot, even if the draft changes later."
footnote="Anyone with the link can view. Uploaded documents are never shared.">
<ShareForm />
</Dialog>