Dialog

Modal built on the native <dialog> element. The browser already knows how to do this properly — it traps focus, closes on Escape, makes the rest of the page inert, and renders on the top layer above every stacking context.

That is the whole reason PresetUI can ship a modal without a focus-trap library. Try tabbing through the demo below with the dialog open: focus never escapes it, and Escape closes it.

Delete this project?

Everything in it — deployments, logs, and environment variables — is removed immediately. This cannot be undone.

Props

PropValuesDefaultDescription
openbooleanrequiredWhether the dialog is showing.
onClosefunctionrequiredCalled for every dismissal.
titlenodeHeading shown next to the close button.
size"sm", "md", "lg""md"Maximum width of the panel.
classNamestring""Appended after the component's own classes.

Common patterns

const [open, setOpen] = useState(false)

<Button onClick={() => setOpen(true)}>Delete project</Button>

<Dialog open={open} onClose={() => setOpen(false)} title="Delete this project?">
  This cannot be undone.
  <div className="mt-6 flex justify-end gap-2">
    <Button size="sm" variant="outline" onClick={() => setOpen(false)}>
      Keep it
    </Button>
    <Button size="sm" variant="danger" onClick={confirmDelete}>
      Delete project
    </Button>
  </div>
</Dialog>

Notes

onClose fires for every dismissal — the close button, Escape, and a click on the backdrop. Always set your state to closed there, or the dialog will be shut while your open prop still says otherwise, and the next open won't work.

The backdrop is the <dialog> element itself, which is why the click handler compares event.target against the element: a click on the content bubbles up to the same node and would otherwise close the dialog.

Style the backdrop with the backdrop: variant — this component uses backdrop:bg-black/50.

There is no built-in animation. Native dialogs need @starting-style and transition-behavior: allow-discrete to animate in and out; that is a few lines of CSS in your stylesheet rather than something this file should carry.