crispmeasured from Attio

Dialog

A modal dialog — Attio's create-record surface (focus-trapped, scroll-locked).

The modal behind Attio's create-record flow: a radius-16 card with a layered float shadow, a bordered header (title + close), a scrollable body, and a bordered footer for the primary action. It composes Zag's dialog machine, so focus trapping, scroll lock, and Escape / outside-click dismissal come from the machine rather than hand-rolled handlers. Give it a title and compose the content with DialogBody / DialogFooter.

Preview

Installation

bun add crisp
import "crisp/styles.css";
import { Dialog, DialogBody, DialogFooter } from "crisp";

Usage

import { Dialog, DialogBody, DialogFooter } from "crisp";

const [open, setOpen] = useState(false);

<Dialog open={open} onOpenChange={setOpen} title="New company" width={520}>
  <DialogBody>
    <div className="dialog-field">
      <label className="dialog-field-label" htmlFor="name">Name</label>
      <input id="name" placeholder="Set Name…" />
    </div>
  </DialogBody>
  <DialogFooter>
    <button className="dialog-submit" onClick={save}>
      Create record <span className="dialog-submit-key">⌘↵</span>
    </button>
  </DialogFooter>
</Dialog>;

API

Dialog

PropTypeDefaultDescription
openbooleanControlled open state — the caller owns when the dialog shows.
onOpenChange(open: boolean) => voidFires on Escape, outside-click, and the close button.
titleReactNodeHeader title; when set, the header (title + close) renders.
iconReactNodeLeading icon shown before the title.
widthnumber640Content width in px, clamped to 90vw.
aria-labelstringAccessible name when title is not a plain string.

Also exported: DialogBody (scrollable, gap-stacked content region) and DialogFooter (right-aligned action bar). The reusable form classes — dialog-field, dialog-field-label, dialog-more, dialog-submit, dialog-submit-key — style stacked label/input rows and the primary action.

Notes

  • Focus & scroll. Opening traps focus inside the dialog and locks background scroll; closing returns focus to the trigger — all from the Zag machine.
  • Dismissal. Escape, an outside pointer-down, and the header close button all call onOpenChange(false); the caller decides whether to actually close.
  • Portal. The dialog renders in a portal at the document root, so it is never clipped by an ancestor's overflow — style its content through the dialog-* classes rather than a wrapping selector.

On this page