mica
Patterns / dialog

dialog

Tier 1

Modal and drawer. Focus trap, Esc, top layer, backdrop — the browser's. Open/close — invoker commands (shimmed while support spreads).

Confirm

This is a description.

Body content sits between header and footer.

Drawer

The same native dialog dressed as a drawer.

Body content; the footer pins to the bottom with stacked actions.

Composition

The shadcn structure, in native vocabulary — header (title + description), body, footer are real elements, not components:

<button commandfor="confirm" command="show-modal">Open</button>  <!-- trigger -->

<dialog id="confirm">          <!-- content -->
  <button class="close" commandfor="confirm" command="close" aria-label="Close">&#x2715;</button>
  <header>                     <!-- header -->
    <h2>Are you sure?</h2>     <!-- title -->
    <p>This cannot be undone.</p>  <!-- description -->
  </header>

  <p>Any body content.</p>

  <footer>                     <!-- footer: right-aligned row -->
    <form method="dialog">
      <button>Cancel</button>
      <button class="primary" value="ok">Confirm</button>
    </form>
  </footer>
</dialog>

Notes

Enter/exit animations use @starting-style + transition-behavior: allow-discrete — the backdrop fades too. dialog.returnValue carries the value of the submitting button.

Bottom sheet + gestures

Below 40rem the drawer becomes a full-width bottom sheet (content height, anchored to the bottom edge — the same pattern as shadcn's mobile drawer). The optional drawer.js module (Tier 2) adds a grab handle and swipe-to-dismiss: the sheet follows a downward drag, resists upward, and closes past a third of its height or on a flick. Without the module the markup still works — the handle only appears when the behavior exists.

<script type="module" src="mica/drawer.js"></script>

On the sheet the handle is the dismiss affordance, so the corner × is redundant there while it stays the only one on the desktop side-sheet. Hide it below the breakpoint in your own CSS — this page does exactly that:

@media (max-width: 40rem) {
  dialog.drawer > button.close { display: none; }
}

Browser support

Invoker commands (commandfor/command) are Baseline newly-available (Chrome 135+, Firefox 144+, Safari 26.2+). Until they're widely available, include the shim — it installs nothing where the browser handles commands natively, and its removal is a one-line diff later:

<script type="module" src="mica/invoker.js"></script>
← field validationaccordion →