Installation
Complete project setup first. Your configuration
must select react/base and include your Design System ID.
pnpm exec frontfriend download dialog -f react --primitive baseReview dependencies reported by the downloader and keep your source changes in version control.
Usage
import {
Dialog,
DialogTrigger,
DialogPortal,
DialogOverlay,
DialogHeader,
DialogTitle,
DialogDescription,
DialogBody,
DialogFooter,
DialogClose,
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { Dialog as DialogPrimitive } from "@base-ui/react/dialog";
import { cn } from "@frontfriend/sdk/lib/react/utils";
import { ffdc, config, iconSet } from "@frontfriend/sdk";
import { Icon } from "@/components/ui/icon";
type DialogDemoProps = {
align?: "end" | "justify" | "start" | "stretch";
padding?: boolean;
closeIcon?: boolean;
height?: "full" | "auto";
width?: "xs" | "sm" | "md" | "lg" | "3xl" | "5xl" | "full" | "fit";
overlay?: boolean;
closeOnOutside?: boolean;
};
const styling = ffdc(config || {}).dialog;
const paragraph =
"Velit mollit eu amet ullamco ad. Est do enim sunt officia id ullamco consectetur tempor eiusmod sit. Fugiat eiusmod veniam velit fugiat ipsum Lorem id eiusmod tempor est. Ex in Lorem occaecat magna eiusmod eiusmod dolor nisi voluptate incididunt eiusmod deserunt cupidatat cillum. Ad Lorem laboris aute commodo ullamco ipsum enim id incididunt dolor. Irure ut non aliqua fugiat mollit voluptate aliquip aliqua voluptate consectetur quis fugiat. Eu pariatur incididunt adipisicing elit sint fugiat dolore mollit.";
function Example({
align = "end",
padding = true,
closeIcon = true,
height = "auto",
width = "md",
overlay = true,
closeOnOutside = true,
scrollable = false,
}: DialogDemoProps & {
scrollable?: boolean;
}) {
return (
<Dialog disablePointerDismissal={!closeOnOutside}>
<DialogTrigger asChild>
<Button variant="tertiary">Open Dialog</Button>
</DialogTrigger>
<DialogPortal>
{overlay && <DialogOverlay />}
<DialogPrimitive.Popup
data-slot="dialog-content"
className={cn(
styling.slots.content,
styling.props.height[height],
styling.props.width[width],
)}
>
<DialogHeader>
<DialogTitle>Dialog Title</DialogTitle>
<DialogDescription>
Make changes to your profile here. Click save when you're done.
</DialogDescription>
</DialogHeader>
<DialogBody padding={padding} className="grid gap-4">
{Array.from({ length: scrollable ? 4 : 1 }, (_, index) => (
<p key={index}>{paragraph}</p>
))}
</DialogBody>
<DialogFooter align={align}>
<DialogClose asChild>
<Button variant="tertiary">Cancel</Button>
</DialogClose>
<DialogClose asChild>
<Button>Save changes</Button>
</DialogClose>
</DialogFooter>
{closeIcon && (
<DialogClose asChild>
<Button
aria-label="Close dialog"
variant="ghost"
size="icon-sm"
className={cn(styling.slots.close)}
>
<Icon name={iconSet.dialog.close} aria-hidden="true" />
</Button>
</DialogClose>
)}
</DialogPrimitive.Popup>
</DialogPortal>
</Dialog>
);
}
<Example align="end" padding closeIcon overlay closeOnOutside height="auto" />; Controlled dialogs
Control the open state through the selected target’s native API, as the examples show. Close only after a successful asynchronous action, and preserve the user’s input when validation fails.
Keep the title and description in the dialog composition. A complex form may need explicit initial focus and an associated error summary; test it in your application.
Accessibility
- Include an accessible title and a useful description.
- Check initial focus, focus containment and focus restoration.
- Test Escape and outside dismissal in the real product context.
- Use an alert dialog for decisions that require explicit acknowledgement; do not silently change a dialog’s role.
Examples
Default
import {
Dialog,
DialogTrigger,
DialogPortal,
DialogOverlay,
DialogHeader,
DialogTitle,
DialogDescription,
DialogBody,
DialogFooter,
DialogClose,
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { Dialog as DialogPrimitive } from "@base-ui/react/dialog";
import { cn } from "@frontfriend/sdk/lib/react/utils";
import { ffdc, config, iconSet } from "@frontfriend/sdk";
import { Icon } from "@/components/ui/icon";
type DialogDemoProps = {
align?: "end" | "justify" | "start" | "stretch";
padding?: boolean;
closeIcon?: boolean;
height?: "full" | "auto";
width?: "xs" | "sm" | "md" | "lg" | "3xl" | "5xl" | "full" | "fit";
overlay?: boolean;
closeOnOutside?: boolean;
};
const styling = ffdc(config || {}).dialog;
const paragraph =
"Velit mollit eu amet ullamco ad. Est do enim sunt officia id ullamco consectetur tempor eiusmod sit. Fugiat eiusmod veniam velit fugiat ipsum Lorem id eiusmod tempor est. Ex in Lorem occaecat magna eiusmod eiusmod dolor nisi voluptate incididunt eiusmod deserunt cupidatat cillum. Ad Lorem laboris aute commodo ullamco ipsum enim id incididunt dolor. Irure ut non aliqua fugiat mollit voluptate aliquip aliqua voluptate consectetur quis fugiat. Eu pariatur incididunt adipisicing elit sint fugiat dolore mollit.";
function Example({
align = "end",
padding = true,
closeIcon = true,
height = "auto",
width = "md",
overlay = true,
closeOnOutside = true,
scrollable = false,
}: DialogDemoProps & {
scrollable?: boolean;
}) {
return (
<Dialog disablePointerDismissal={!closeOnOutside}>
<DialogTrigger asChild>
<Button variant="tertiary">Open Dialog</Button>
</DialogTrigger>
<DialogPortal>
{overlay && <DialogOverlay />}
<DialogPrimitive.Popup
data-slot="dialog-content"
className={cn(
styling.slots.content,
styling.props.height[height],
styling.props.width[width],
)}
>
<DialogHeader>
<DialogTitle>Dialog Title</DialogTitle>
<DialogDescription>
Make changes to your profile here. Click save when you're done.
</DialogDescription>
</DialogHeader>
<DialogBody padding={padding} className="grid gap-4">
{Array.from({ length: scrollable ? 4 : 1 }, (_, index) => (
<p key={index}>{paragraph}</p>
))}
</DialogBody>
<DialogFooter align={align}>
<DialogClose asChild>
<Button variant="tertiary">Cancel</Button>
</DialogClose>
<DialogClose asChild>
<Button>Save changes</Button>
</DialogClose>
</DialogFooter>
{closeIcon && (
<DialogClose asChild>
<Button
aria-label="Close dialog"
variant="ghost"
size="icon-sm"
className={cn(styling.slots.close)}
>
<Icon name={iconSet.dialog.close} aria-hidden="true" />
</Button>
</DialogClose>
)}
</DialogPrimitive.Popup>
</DialogPortal>
</Dialog>
);
}
<Example align="end" padding closeIcon overlay closeOnOutside height="auto" />; Scrollable
import {
Dialog,
DialogTrigger,
DialogPortal,
DialogOverlay,
DialogHeader,
DialogTitle,
DialogDescription,
DialogBody,
DialogFooter,
DialogClose,
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { Dialog as DialogPrimitive } from "@base-ui/react/dialog";
import { cn } from "@frontfriend/sdk/lib/react/utils";
import { ffdc, config, iconSet } from "@frontfriend/sdk";
import { Icon } from "@/components/ui/icon";
type DialogDemoProps = {
align?: "end" | "justify" | "start" | "stretch";
padding?: boolean;
closeIcon?: boolean;
height?: "full" | "auto";
width?: "xs" | "sm" | "md" | "lg" | "3xl" | "5xl" | "full" | "fit";
overlay?: boolean;
closeOnOutside?: boolean;
};
const styling = ffdc(config || {}).dialog;
const paragraph =
"Velit mollit eu amet ullamco ad. Est do enim sunt officia id ullamco consectetur tempor eiusmod sit. Fugiat eiusmod veniam velit fugiat ipsum Lorem id eiusmod tempor est. Ex in Lorem occaecat magna eiusmod eiusmod dolor nisi voluptate incididunt eiusmod deserunt cupidatat cillum. Ad Lorem laboris aute commodo ullamco ipsum enim id incididunt dolor. Irure ut non aliqua fugiat mollit voluptate aliquip aliqua voluptate consectetur quis fugiat. Eu pariatur incididunt adipisicing elit sint fugiat dolore mollit.";
function Example({
align = "end",
padding = true,
closeIcon = true,
height = "auto",
width = "md",
overlay = true,
closeOnOutside = true,
scrollable = false,
}: DialogDemoProps & {
scrollable?: boolean;
}) {
return (
<Dialog disablePointerDismissal={!closeOnOutside}>
<DialogTrigger asChild>
<Button variant="tertiary">Open Dialog</Button>
</DialogTrigger>
<DialogPortal>
{overlay && <DialogOverlay />}
<DialogPrimitive.Popup
data-slot="dialog-content"
className={cn(
styling.slots.content,
styling.props.height[height],
styling.props.width[width],
)}
>
<DialogHeader>
<DialogTitle>Dialog Title</DialogTitle>
<DialogDescription>
Make changes to your profile here. Click save when you're done.
</DialogDescription>
</DialogHeader>
<DialogBody padding={padding} className="grid gap-4">
{Array.from({ length: scrollable ? 4 : 1 }, (_, index) => (
<p key={index}>{paragraph}</p>
))}
</DialogBody>
<DialogFooter align={align}>
<DialogClose asChild>
<Button variant="tertiary">Cancel</Button>
</DialogClose>
<DialogClose asChild>
<Button>Save changes</Button>
</DialogClose>
</DialogFooter>
{closeIcon && (
<DialogClose asChild>
<Button
aria-label="Close dialog"
variant="ghost"
size="icon-sm"
className={cn(styling.slots.close)}
>
<Icon name={iconSet.dialog.close} aria-hidden="true" />
</Button>
</DialogClose>
)}
</DialogPrimitive.Popup>
</DialogPortal>
</Dialog>
);
}
<Example align="end" padding closeIcon scrollable />; API reference
Frontfriend-owned props. Native HTML and inherited primitive props remain documented by Base UI.
Dialog
| Prop | Type | Default | Description |
|---|---|---|---|
overlay | boolean | true |
DialogBody
| Prop | Type | Default | Description |
|---|---|---|---|
padding | boolean | true |
DialogFooter
| Prop | Type | Default | Description |
|---|---|---|---|
align | "end" | "justify" | "start" | "stretch" | "end" | |
showCloseButton | boolean | false |
DialogHeader
| Prop | Type | Default | Description |
|---|---|---|---|
closeIcon | boolean | false |
Configuration determines the actual classes and available styling hooks. See Theming & component configuration before introducing a repeated local override.