Dialog
Installation
npm install @radix-ui/react-dialog@^1.1.15 @tabler/icons-react@^3.36.1 class-variance-authority@^0.7.1 clsx@^2.1.1 tailwind-merge@^3.4.0components/overlay-layout/overlay-layout.ts
lib/icons.ts
lib/utils.ts
components/dialog/dialog.tsx
components/dialog/index.tsThe code below also uses:
npm install @radix-ui/react-slot@^1.2.4components/button-group/button-group-context.ts
components/spinner/index.ts
components/spinner/spinner.tsx
components/button/button.tsx
components/button/index.tsCode
'use client';
import { Button } from '@/components/button/button';
import {
Dialog,
DialogBody,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from '@/components/dialog/dialog';
export default function DialogDemo() {
return (
<Dialog>
<DialogTrigger asChild>
<Button variant="outline">Archive project</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Archive project?</DialogTitle>
<DialogDescription>
Archived projects are hidden from the sidebar and stop sending
notifications.
</DialogDescription>
</DialogHeader>
<DialogBody>
<p className="nx:typography-body-default nx:text-foreground">
You can restore the project from workspace settings at any time.
</p>
</DialogBody>
<DialogFooter>
<DialogClose asChild>
<Button variant="outline">Cancel</Button>
</DialogClose>
<DialogClose asChild>
<Button>Archive</Button>
</DialogClose>
</DialogFooter>
</DialogContent>
</Dialog>
);
}
Props
Dialog
Dialog adds no props of its own; it accepts the props of the element or primitive it wraps.
DialogBody
DialogBody adds no props of its own; it accepts the props of the element or primitive it wraps.
DialogClose
DialogClose adds no props of its own; it accepts the props of the element or primitive it wraps.
DialogContent
| Prop | Type | Default | Description |
|---|---|---|---|
body | ReactNode | No default documented | Optional body content rendered in a generated |
description | ReactNode | No default documented | Optional description for a prop-driven header, rendered before |
showCloseButton | boolean | true | Whether to show the close button in the top-right corner. |
title | ReactNode | No default documented | Optional title for a prop-driven header, rendered before |
DialogDescription
DialogDescription adds no props of its own; it accepts the props of the element or primitive it wraps.
DialogFooter
DialogFooter adds no props of its own; it accepts the props of the element or primitive it wraps.
DialogHeader
DialogHeader adds no props of its own; it accepts the props of the element or primitive it wraps.
DialogOverlay
DialogOverlay adds no props of its own; it accepts the props of the element or primitive it wraps.
DialogPortal
DialogPortal adds no props of its own; it accepts the props of the element or primitive it wraps.
DialogTitle
DialogTitle adds no props of its own; it accepts the props of the element or primitive it wraps.
DialogTrigger
DialogTrigger adds no props of its own; it accepts the props of the element or primitive it wraps.
Examples
With form
Display name: Ada Lovelace
This example also needs:
npm install @radix-ui/react-label@^2.1.7lib/field-size.ts
components/input/index.ts
components/input/input.tsx
components/label/index.ts
components/label/label.tsx'use client';
import { type FormEvent, useState } from 'react';
import { Button } from '@/components/button/button';
import {
Dialog,
DialogBody,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from '@/components/dialog/dialog';
import { Input } from '@/components/input/input';
import { Label } from '@/components/label/label';
export default function DialogWithForm() {
const [open, setOpen] = useState(false);
const [name, setName] = useState('Ada Lovelace');
function handleSubmit(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
const data = new FormData(event.currentTarget);
setName(String(data.get('name')));
setOpen(false);
}
return (
<div className="nx:flex nx:flex-col nx:items-center nx:gap-3">
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<Button variant="outline">Edit profile</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Edit profile</DialogTitle>
<DialogDescription>
Change how your name appears to teammates.
</DialogDescription>
</DialogHeader>
<DialogBody>
<form
id="dialog-with-form"
className="nx:grid nx:gap-1.5"
onSubmit={handleSubmit}
>
<Label htmlFor="dialog-with-form-name">Display name</Label>
<Input
id="dialog-with-form-name"
name="name"
defaultValue={name}
required
/>
</form>
</DialogBody>
<DialogFooter>
<DialogClose asChild>
<Button variant="outline">Cancel</Button>
</DialogClose>
<Button type="submit" form="dialog-with-form">
Save changes
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
<p className="nx:typography-body-default nx:text-muted-foreground">
Display name: {name}
</p>
</div>
);
}
Prop driven
'use client';
import { Button } from '@/components/button/button';
import {
Dialog,
DialogClose,
DialogContent,
DialogFooter,
DialogTrigger,
} from '@/components/dialog/dialog';
export default function DialogPropDriven() {
return (
<Dialog>
<DialogTrigger asChild>
<Button variant="outline">Invite teammate</Button>
</DialogTrigger>
<DialogContent
title="Invite teammate"
description="Send an invitation and assign their first workspace role."
body={
<p className="nx:typography-body-default nx:text-foreground">
The invitation expires in seven days and can be revoked from team
settings.
</p>
}
>
<DialogFooter>
<DialogClose asChild>
<Button variant="outline">Cancel</Button>
</DialogClose>
<DialogClose asChild>
<Button>Send invite</Button>
</DialogClose>
</DialogFooter>
</DialogContent>
</Dialog>
);
}
Scrollable body
'use client';
import { Button } from '@/components/button/button';
import {
Dialog,
DialogBody,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from '@/components/dialog/dialog';
const SECTIONS = [
'Your workspace data stays in the region you chose at sign-up.',
'Admins can export or delete workspace data from settings at any time.',
'Integrations only receive the scopes you approve when connecting them.',
'Audit logs record every sign-in, permission change and export.',
'Deleted projects are kept for thirty days before permanent removal.',
'Billing contacts receive invoices and usage alerts by email.',
'Single sign-on is enforced for every member once an admin enables it.',
'Guests can only see the projects they are explicitly invited to.',
];
export default function DialogScrollableBody() {
return (
<Dialog>
<DialogTrigger asChild>
<Button variant="outline">Review terms</Button>
</DialogTrigger>
<DialogContent className="nx:h-80">
<DialogHeader>
<DialogTitle>Workspace terms</DialogTitle>
<DialogDescription>
The body scrolls while the header and footer stay in place.
</DialogDescription>
</DialogHeader>
<DialogBody className="nx:space-y-4 nx:typography-body-default nx:text-foreground">
{SECTIONS.map((section) => (
<p key={section}>{section}</p>
))}
</DialogBody>
<DialogFooter>
<DialogClose asChild>
<Button variant="outline">Decline</Button>
</DialogClose>
<DialogClose asChild>
<Button>Accept</Button>
</DialogClose>
</DialogFooter>
</DialogContent>
</Dialog>
);
}