Dialog
Dialog allows you to create modal elements that overlay the main content.
'use client';
import { Button } from '@/components/button';
import {
Dialog,
DialogActions,
DialogClose,
DialogContent,
DialogDescription,
DialogTitle,
DialogTrigger,
} from '@/components/dialog';
export default function DialogPreview() {
return (
<Dialog>
<DialogTrigger asChild>
<Button variant="outline">Open</Button>
</DialogTrigger>
<DialogContent className="w-80">
<DialogTitle>Unsaved changes</DialogTitle>
<DialogDescription>
Are you sure you want to leave this page?
</DialogDescription>
<DialogActions>
<DialogClose asChild>
<Button>Confirm</Button>
</DialogClose>
<DialogClose asChild>
<Button variant="outline">Cancel</Button>
</DialogClose>
</DialogActions>
</DialogContent>
</Dialog>
);
} Dependencies
Source Code
'use client';
import {
Modal,
ModalClose,
ModalContent,
ModalDescription,
ModalTitle,
ModalTrigger,
} from '@/components/modal';
import { cn } from '@/lib/utils/classnames';
type DialogProps = React.ComponentProps<typeof Modal>;
const Dialog = ({ ...props }: DialogProps) => {
return <Modal {...props} />;
};
interface DialogContentProps extends React.ComponentProps<typeof ModalContent> {
align?: 'center' | 'top';
}
const DialogContent = ({
className,
children,
align = 'center',
...props
}: DialogContentProps) => {
return (
<ModalContent
className={cn(
'm-auto w-full max-w-[calc(100vw-(--spacing(8)))] rounded-3xl border border-border bg-background p-4 shadow-lg md:max-w-md',
'max-h-[calc(100svh-2rem)] overflow-y-auto backdrop:bg-black/20 backdrop:backdrop-blur-sm',
'transition-all duration-300 backdrop:transition-all motion-reduce:transition-none motion-reduce:backdrop:transition-none',
'not-data-[status=open]:translate-y-2 not-data-[status=open]:scale-95 not-data-[status=open]:opacity-0 not-data-[status=open]:duration-150 not-data-[status=open]:backdrop:opacity-0',
align === 'top' && 'mt-4',
className
)}
{...props}
>
{children}
</ModalContent>
);
};
const DialogTrigger = ModalTrigger;
const DialogClose = ModalClose;
const DialogTitle = ({
children,
className,
...props
}: React.ComponentProps<typeof ModalTitle>) => {
return (
<ModalTitle className={cn('pb-2 font-semibold', className)} {...props}>
{children}
</ModalTitle>
);
};
const DialogDescription = ({
children,
className,
...props
}: React.ComponentProps<typeof ModalDescription>) => {
return (
<ModalDescription className={cn('pb-2', className)} {...props}>
{children}
</ModalDescription>
);
};
const DialogActions = ({
className,
children,
...props
}: React.ComponentPropsWithRef<'div'>) => (
<div
className={cn(
'flex flex-col gap-2 pt-4 sm:flex-row sm:justify-start',
className
)}
{...props}
>
{children}
</div>
);
export {
Dialog,
DialogActions,
DialogClose,
DialogContent,
DialogDescription,
DialogTitle,
DialogTrigger,
}; Features
- Modal Overlay: Creates an accessible modal dialog with backdrop
- Focus Management: Automatically traps focus within the dialog
- Flexible Positioning: Center or top alignment options
- Controlled & Uncontrolled: Supports both controlled and uncontrolled modes
- Customizable Actions: Built-in support for common dialog actions
Anatomy
<Dialog>
<DialogTrigger />
<DialogContent>
<DialogTitle />
<DialogClose />
<DialogDescription />
<DialogActions />
</Dialog.Content>
</Dialog>
API Reference
Dialog
Extends the Modal component.
DialogTrigger
Extends the ModalTrigger component.
DialogContent
Extends the ModalContent component.
| Prop | Default | Type | Description |
|---|---|---|---|
align | "center" | "center""top" | The vertical alignment of the dialog. |
DialogTitle
Extends the ModalTitle component.
DialogDescription
Extends the ModalDescription component.
DialogActions
Extends the div element.
This component is designed to be placed at the bottom of the dialog content, providing a consistent layout for action buttons such as “Cancel” and “Confirm”.
DialogClose
Extends the ModalClose component.
| Prop | Default | Type | Description |
|---|---|---|---|
asChild | - | boolean | Whether to merge props onto the child element. |
Examples
Simple
A basic dialog with a title and actions.
'use client';
import { Button } from '@/components/button';
import {
Dialog,
DialogActions,
DialogClose,
DialogContent,
DialogDescription,
DialogTitle,
DialogTrigger,
} from '@/components/dialog';
export default function DialogPreview() {
return (
<Dialog>
<DialogTrigger asChild>
<Button variant="outline">Open</Button>
</DialogTrigger>
<DialogContent className="w-80">
<DialogTitle>Unsaved changes</DialogTitle>
<DialogDescription>
Are you sure you want to leave this page?
</DialogDescription>
<DialogActions>
<DialogClose asChild>
<Button>Confirm</Button>
</DialogClose>
<DialogClose asChild>
<Button variant="outline">Cancel</Button>
</DialogClose>
</DialogActions>
</DialogContent>
</Dialog>
);
} Align to the top
Dialog aligned to the top of the screen.
'use client';
import { Button } from '@/components/button';
import {
Dialog,
DialogActions,
DialogClose,
DialogContent,
DialogDescription,
DialogTitle,
DialogTrigger,
} from '@/components/dialog';
export default function DialogTopPreview() {
return (
<Dialog>
<DialogTrigger asChild>
<Button variant="outline">Open at the top</Button>
</DialogTrigger>
<DialogContent align="top">
<DialogTitle>Unsaved changes</DialogTitle>
<DialogDescription>
Are you sure you want to leave this page?
</DialogDescription>
<DialogActions>
<Button>Confirm</Button>
<DialogClose asChild>
<Button variant="outline">Cancel</Button>
</DialogClose>
</DialogActions>
</DialogContent>
</Dialog>
);
} Destructive
A dialog for destructive actions with appropriate styling.
'use client';
import { Button } from '@/components/button';
import {
Dialog,
DialogActions,
DialogClose,
DialogContent,
DialogDescription,
DialogTitle,
DialogTrigger,
} from '@/components/dialog';
export default function DialogDestructivePreview() {
return (
<Dialog>
<DialogTrigger asChild>
<Button variant="destructive">Delete</Button>
</DialogTrigger>
<DialogContent className="w-80">
<DialogTitle>Are you sure?</DialogTitle>
<DialogDescription>
This action cannot be undone. This will permanently delete your
account and remove your data from our servers.
</DialogDescription>
<DialogActions>
<Button variant="destructive">Delete everything</Button>
<DialogClose asChild>
<Button variant="outline">Cancel</Button>
</DialogClose>
</DialogActions>
</DialogContent>
</Dialog>
);
} Tall content
Dialog with scrollable content.
'use client';
import { Button } from '@/components/button';
import {
Dialog,
DialogActions,
DialogClose,
DialogContent,
DialogTitle,
DialogTrigger,
} from '@/components/dialog';
export default function DialogTallPreview() {
return (
<Dialog>
<DialogTrigger asChild>
<Button variant="outline">Open Tall Dialog</Button>
</DialogTrigger>
<DialogContent>
<DialogTitle>Very Tall Dialog</DialogTitle>
<div>
{Array(20)
.fill(null)
.map((_, index) => (
<p key={index} className="mb-4">
This is paragraph {index + 1}. Lorem ipsum dolor sit amet,
consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut
labore et dolore magna aliqua.
</p>
))}
</div>
<DialogActions>
<DialogClose asChild>
<Button variant="outline">Close</Button>
</DialogClose>
</DialogActions>
</DialogContent>
</Dialog>
);
} Arbitrary content
Dialog with custom content layout.
import { XIcon } from '@phosphor-icons/react/dist/ssr';
import { Button } from '@/components/button';
import {
Dialog,
DialogClose,
DialogContent,
DialogTrigger,
} from '@/components/dialog';
export default function DialogArbitraryPreview() {
return (
<Dialog>
<DialogTrigger asChild>
<Button variant="outline">Open Dialog</Button>
</DialogTrigger>
<DialogContent className="p-0">
<DialogClose className="absolute top-3 right-3" asChild>
<Button variant="outline" size="sm" square>
<XIcon className="size-4" />
</Button>
</DialogClose>
<div className="flex flex-col items-center rounded-2xl p-6">
<div className="mb-4 size-24 rounded-full border border-border bg-background-secondary" />
<h2 className="font-semibold text-xl">John Doe</h2>
<p className="mb-4 text-foreground-secondary text-sm">
Software Engineer
</p>
<div className="mb-6 flex space-x-4">
<div className="text-center">
<p className="font-semibold">1.2k</p>
<p className="text-foreground-secondary text-xs">Followers</p>
</div>
<div className="text-center">
<p className="font-semibold">3.4k</p>
<p className="text-foreground-secondary text-xs">Following</p>
</div>
<div className="text-center">
<p className="font-semibold">567</p>
<p className="text-foreground-secondary text-xs">Posts</p>
</div>
<Button>Follow</Button>
</div>
</DialogContent>
</Dialog>
);
} Best Practices
-
Content Structure:
- Always include a clear title that describes the purpose
- Keep content concise and focused
- Use appropriate action labels (avoid “OK/Cancel”)
-
Mobile Considerations:
- Test on different screen sizes
- Ensure touch targets are large enough
- Consider native scroll behavior for tall content
-
Performance:
- Lazy load dialog content if needed
- Consider using dynamic imports for heavy content
- Clean up resources when dialog closes
Previous
Date Picker
Next
Disclosure