Components
Modal
Overlay dialogs built on React Aria's Modal/Dialog, in three shapes: a neutral Confirmation prompt, a Destructive warning, and a free-form Form modal for arbitrary content.
Confirmation
For reversible or low-stakes actions. Every modal copies Untitled UI's own modal anatomy (its stacked left-aligned and horizontal examples): the close X floating in the top-right corner, a featured icon, the title with the description under it, and two equal-width buttons (secondary, then primary) that stack on a phone. The page behind is dimmed (black at 40%) and blurred.
Confirmation modal
Destructive
For irreversible actions - delete, remove, revoke, discard. Same shape as Confirmation, with an error icon and the primary button in the error colour, so the stakes read at a glance. A discard prompt can offer a way to keep the work (secondaryLabel, onSecondary): every form's discard prompt offers Save draft where a draft is possible. Three actions don't fit the two-column row, so that prompt switches to Untitled UI's horizontal layout: the icon beside the title and the buttons right-aligned.
Destructive modal
Form (configurable)
A general-purpose shell - header, scrollable body, footer - that takes arbitrary children instead of a fixed title and description. Wraps content in React Aria's Form, so a submit button with type="submit" triggers native validation before onSubmit fires.
Form modal
Usage
import { useState } from "react";
import { ConfirmationModal, DestructiveModal, FormModal } from "@/components/application/modals/modal";
import { Input } from "@/components/base/input/input";
const [isOpen, setIsOpen] = useState(false);
<ConfirmationModal
isOpen={isOpen}
onOpenChange={setIsOpen}
title="Leave this page?"
description="You have unsaved changes."
confirmLabel="Leave"
confirmIcon={ArrowLeft}
onConfirm={() => setIsOpen(false)}
/>
<DestructiveModal
isOpen={isOpen}
onOpenChange={setIsOpen}
title="Delete this project?"
description="This action can't be undone."
confirmIcon={Trash01}
onConfirm={handleDelete}
/>
<FormModal
isOpen={isOpen}
onOpenChange={setIsOpen}
title="Invite a team member"
submitIcon={Send01}
onSubmit={handleInvite}
>
<Input label="Email address" isRequired />
</FormModal>Composing your own
All three are built from the same primitives, exported alongside them for fully custom layouts: ModalOverlay, Modal, and Dialog. A plain Button or CloseButton with slot="close" closes the dialog without any extra wiring.
import { ModalOverlay, Modal, Dialog } from "@/components/application/modals/modal";
import { CloseButton } from "@/components/base/buttons/close-button";
import { ArrowLeft, Send01, Trash01 } from "@untitledui/icons";
<ModalOverlay isOpen={isOpen} onOpenChange={setIsOpen}>
<Modal className="w-full max-w-md">
<Dialog>
<div className="p-6">
<CloseButton size="sm" />
{/* anything at all */}
</div>
</Dialog>
</Modal>
</ModalOverlay>API
| Component | Key props |
|---|---|
ConfirmationModal | isOpen, onOpenChange, icon (default HelpCircle), iconColor (brand | gray | success | warning), title, description, confirmLabel, confirmIcon (required: the icon that names the action), cancelLabel, cancelIcon (default XClose), onConfirm, isConfirmLoading |
DestructiveModal | isOpen, onOpenChange, icon (default AlertTriangle), title, description, confirmLabel (default "Delete"), confirmIcon (required), cancelLabel, cancelIcon (default XClose), onConfirm, isConfirmLoading, secondaryLabel, secondaryIcon (default Save01), onSecondary (an optional middle action, e.g. Save draft) |
FormModal | isOpen, onOpenChange, icon, iconColor (default gray), title, description, children, submitLabel, submitIcon (required), submitIconTrailing (an arrow after the label, for Continue), cancelLabel, cancelIcon (default XClose), onSubmit, isSubmitLoading, size (sm 400px | md 480px | lg 640px) |
ModalHeader, ModalFooter | The shared anatomy, for an app modal that composes its own body. ModalHeader: icon, iconColor, title, description, layout (stacked | horizontal); it also renders the floating close X. ModalFooter: layout, children (the buttons, primary last). |