Modal
Modal and AlertModal flatten shadcn's Dialog / AlertDialog compound components into single components with title, description, and footer props — plus promise-style AlertModal.alert() / AlertModal.confirm() helpers and a full imperative API powered by @easy-shadcn/command-modal.
Installation
With the @easy-shadcn namespace configured:
pnpm dlx shadcn@latest add @easy-shadcn/modalOr install via the full URL (zero configuration):
pnpm dlx shadcn@latest add https://easy-shadcn.vercel.app/r/modal.jsonThe internal @easy-shadcn/async-button component and the @easy-shadcn/command-modal package are installed automatically alongside modal.
Usage
Modal
The flat onConfirm / onCancel footer needs no controlled state — confirm closes the modal after the (optionally async) handler resolves, and a rejection keeps it open:
import { Modal } from "@/components/easy/modal"
<Modal
title="Publish post"
description="The post goes live immediately."
trigger={<Button>Publish</Button>}
onConfirm={async () => {
await publish()
}}
>
Body content
</Modal>AlertModal
A declarative confirm dialog with the same footer semantics:
alert() / confirm() helpers
For the imperative helpers, mount the provider once near the root:
import { Modal } from "@/components/easy/modal"
const App = ({ children }) => (
<Modal.Provider>{children}</Modal.Provider>
)Then call them from anywhere — alert() resolves when dismissed, confirm() resolves true / false and never rejects:
import { AlertModal } from "@/components/easy/modal"
await AlertModal.alert({ title: "Saved", description: "All changes stored." })
const ok = await AlertModal.confirm({
title: "Delete item?",
description: "This cannot be undone.",
confirmProps: { variant: "destructive" },
})Imperative modals with useModalHolder
Any component created with Modal.create can be driven imperatively — useModalHolder additionally lets you update its props after opening:
Current Count: 0
API
Modal
| Prop | Type | Default | Description |
|---|---|---|---|
title / description | ReactNode | - | Header slots. The header renders only when one of them is present. |
children | ReactNode | - | Modal body. |
footer | ReactNode | - | Replaces the default footer entirely. |
onConfirm / onCancel | () => void | Promise<void> | - | Enables the default OK/Cancel footer. Async handlers show a pending state; a rejection keeps the modal open. |
confirmText / cancelText | ReactNode | "OK" / "Cancel" | Default footer button labels. |
confirmProps / cancelProps | AsyncButtonProps | - | Extra props for the default footer buttons (e.g. { variant: "destructive" }). |
open / defaultOpen / onOpenChange | - | - | Controlled / uncontrolled open state. |
trigger | ReactElement | - | Rendered as the DialogTrigger. |
showCloseButton | boolean | true | The top-right close button. |
disablePointerDismissal | boolean | false | Prevents closing via outside clicks. |
onOpenChangeComplete | (open: boolean) => void | - | Base UI's post-transition hook, fired after the open/close animation completes. Forwarded to the underlying Dialog. |
Class overrides: className (dialog content), headerClassName, titleClassName, descriptionClassName, contentClassName (body), footerClassName.
AlertModal
Same flat footer contract as Modal, plus:
| Prop | Type | Default | Description |
|---|---|---|---|
showCancel | boolean | true | Hide the cancel button for single-action alerts. |
size | "default" | "sm" | "default" | Forwarded to the AlertDialogContent primitive. |
AlertModal.alert(props) / AlertModal.confirm(props)
Promise-style helpers (require <Modal.Provider>):
alert(props)→Promise<void>— a single OK button; resolves when confirmed or dismissed.confirm(props)→Promise<boolean>— resolvestrueon confirm,falseon cancel or dismissal (Escape). It never rejects, so fire-and-forget calls are safe.- Both accept
AlertModalProps(minus the open-state props) and clean up their modal registry entry after closing.
Imperative API on Modal.*
Modal.create, Modal.show, Modal.hide, Modal.remove, Modal.register, Modal.unregister, Modal.useModal, Modal.useModalHolder, and Modal.Provider re-export the public @easy-shadcn/command-modal surface. See the command-modal docs for the full contract.
Notes
- Accessibility name.
titleis optional in the types, but a dialog without a title has no accessible name — pass one (or render your ownDialogTitlevia the primitives) in production. - For layouts beyond these slots (multi-step wizards, custom headers, side sheets), compose the
components/ui/dialogprimitives directly.