Launch prototype

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

ComponentKey props
ConfirmationModalisOpen, 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
DestructiveModalisOpen, 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)
FormModalisOpen, 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, ModalFooterThe 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).