Fiber UI LogoFiberUI

Dialog

A modal dialog overlay that blocks interaction with the rest of the page. Supports focus trapping, keyboard dismiss, accessibility, multiple sizes, and composable header/body/footer layout.

The Dialog component provides a modal overlay that blocks interaction with the rest of the page. Built on React Aria's Dialog, Modal, and ModalOverlay primitives with full keyboard navigation, focus trapping, and screen reader support.

Basic Usage

A simple dialog with a trigger button, header, body, and footer. Click outside or press Escape to dismiss.

"use client";
import { Button } from "@repo/ui/components/button";
import {
    DialogTrigger,
    DialogOverlay,
    DialogContent,
    DialogHeader,
    DialogTitle,
    DialogDescription,
    DialogBody,
    DialogFooter,
    DialogClose,
} from "@repo/ui/components/dialog";

/* BASIC USAGE EXAMPLE */
export const Example1 = () => {
    return (
        <DialogTrigger>
            <Button variant="outline">Open Dialog</Button>
            <DialogOverlay>
                <DialogContent>
                    <DialogClose />
                    <DialogHeader>
                        <DialogTitle>Welcome to FiberUI</DialogTitle>
                        <DialogDescription>
                            This is a basic dialog built with React Aria
                            components. It supports keyboard navigation, focus
                            trapping, and screen reader accessibility out of the
                            box.
                        </DialogDescription>
                    </DialogHeader>
                    <DialogBody>
                        <p className="text-muted-foreground text-sm">
                            Dialogs are used to display content that requires
                            user attention or interaction. Click outside or
                            press Escape to dismiss.
                        </p>
                    </DialogBody>
                    <DialogFooter>
                        <DialogClose>
                            <Button variant="outline">Cancel</Button>
                        </DialogClose>
                        <DialogClose>
                            <Button>Continue</Button>
                        </DialogClose>
                    </DialogFooter>
                </DialogContent>
            </DialogOverlay>
        </DialogTrigger>
    );
};

Confirmation Dialog

A destructive confirmation dialog for dangerous actions. Uses size="sm" to keep the dialog compact.

"use client";
import { Button } from "@repo/ui/components/button";
import {
    DialogTrigger,
    DialogOverlay,
    DialogContent,
    DialogHeader,
    DialogTitle,
    DialogDescription,
    DialogBody,
    DialogFooter,
    DialogClose,
} from "@repo/ui/components/dialog";

/* CONFIRMATION DIALOG EXAMPLE */
export const Example2 = () => {
    return (
        <DialogTrigger>
            <Button variant="destructive">Delete Account</Button>
            <DialogOverlay>
                <DialogContent size="sm">
                    <DialogHeader>
                        <DialogTitle>Are you absolutely sure?</DialogTitle>
                        <DialogDescription>
                            This action cannot be undone. This will permanently
                            delete your account and remove your data from our
                            servers.
                        </DialogDescription>
                    </DialogHeader>
                    <DialogBody>
                        <div className="border-destructive/20 bg-destructive/5 rounded-md border p-3">
                            <p className="text-destructive text-sm font-medium">
                                Warning: All your projects, files, and settings
                                will be permanently removed.
                            </p>
                        </div>
                    </DialogBody>
                    <DialogFooter>
                        <DialogClose>
                            <Button variant="outline">Cancel</Button>
                        </DialogClose>
                        <DialogClose>
                            <Button variant="destructive">
                                Yes, delete account
                            </Button>
                        </DialogClose>
                    </DialogFooter>
                </DialogContent>
            </DialogOverlay>
        </DialogTrigger>
    );
};

Form Dialog

Embed form elements inside a dialog. All inputs receive proper focus management automatically.

"use client";
import { Button } from "@repo/ui/components/button";
import { Input } from "@repo/ui/components/input";
import { Label } from "@repo/ui/components/label";
import {
    DialogTrigger,
    DialogOverlay,
    DialogContent,
    DialogHeader,
    DialogTitle,
    DialogDescription,
    DialogBody,
    DialogFooter,
    DialogClose,
} from "@repo/ui/components/dialog";

/* FORM DIALOG EXAMPLE */
export const Example3 = () => {
    return (
        <DialogTrigger>
            <Button>Edit Profile</Button>
            <DialogOverlay>
                <DialogContent>
                    <DialogClose />
                    <DialogHeader>
                        <DialogTitle>Edit Profile</DialogTitle>
                        <DialogDescription>
                            Make changes to your profile here. Click save when
                            you&apos;re done.
                        </DialogDescription>
                    </DialogHeader>
                    <DialogBody>
                        <div className="grid gap-4">
                            <div className="grid gap-2">
                                <Label htmlFor="name">Name</Label>
                                <Input
                                    id="name"
                                    placeholder="Enter your name"
                                    defaultValue="Rajat Verma"
                                />
                            </div>
                            <div className="grid gap-2">
                                <Label htmlFor="email">Email</Label>
                                <Input
                                    id="email"
                                    type="email"
                                    placeholder="Enter your email"
                                    defaultValue="rajat@fiberui.com"
                                />
                            </div>
                            <div className="grid gap-2">
                                <Label htmlFor="username">Username</Label>
                                <Input
                                    id="username"
                                    placeholder="Enter username"
                                    defaultValue="@rajatverma"
                                />
                            </div>
                        </div>
                    </DialogBody>
                    <DialogFooter>
                        <DialogClose>
                            <Button variant="outline">Cancel</Button>
                        </DialogClose>
                        <DialogClose>
                            <Button>Save Changes</Button>
                        </DialogClose>
                    </DialogFooter>
                </DialogContent>
            </DialogOverlay>
        </DialogTrigger>
    );
};

Sizes

The DialogContent component supports sm, default, lg, and full size variants.

"use client";
import { Button } from "@repo/ui/components/button";
import {
    DialogTrigger,
    DialogOverlay,
    DialogContent,
    DialogHeader,
    DialogTitle,
    DialogDescription,
    DialogFooter,
    DialogClose,
} from "@repo/ui/components/dialog";

/* SIZE VARIANTS EXAMPLE */
export const Example4 = () => {
    return (
        <div className="flex flex-wrap gap-3">
            <DialogTrigger>
                <Button variant="outline" size="sm">
                    Small
                </Button>
                <DialogOverlay>
                    <DialogContent size="sm">
                        <DialogClose />
                        <DialogHeader>
                            <DialogTitle>Small Dialog</DialogTitle>
                            <DialogDescription>
                                This dialog uses the &quot;sm&quot; size variant
                                with a max-width of 24rem.
                            </DialogDescription>
                        </DialogHeader>
                        <DialogFooter>
                            <DialogClose>
                                <Button variant="outline" size="sm">
                                    Close
                                </Button>
                            </DialogClose>
                        </DialogFooter>
                    </DialogContent>
                </DialogOverlay>
            </DialogTrigger>

            <DialogTrigger>
                <Button variant="outline" size="sm">
                    Default
                </Button>
                <DialogOverlay>
                    <DialogContent size="default">
                        <DialogClose />
                        <DialogHeader>
                            <DialogTitle>Default Dialog</DialogTitle>
                            <DialogDescription>
                                This dialog uses the &quot;default&quot; size
                                variant with a max-width of 32rem.
                            </DialogDescription>
                        </DialogHeader>
                        <DialogFooter>
                            <DialogClose>
                                <Button variant="outline" size="sm">
                                    Close
                                </Button>
                            </DialogClose>
                        </DialogFooter>
                    </DialogContent>
                </DialogOverlay>
            </DialogTrigger>

            <DialogTrigger>
                <Button variant="outline" size="sm">
                    Large
                </Button>
                <DialogOverlay>
                    <DialogContent size="lg">
                        <DialogClose />
                        <DialogHeader>
                            <DialogTitle>Large Dialog</DialogTitle>
                            <DialogDescription>
                                This dialog uses the &quot;lg&quot; size variant
                                with a max-width of 42rem. Ideal for more
                                complex content.
                            </DialogDescription>
                        </DialogHeader>
                        <DialogFooter>
                            <DialogClose>
                                <Button variant="outline" size="sm">
                                    Close
                                </Button>
                            </DialogClose>
                        </DialogFooter>
                    </DialogContent>
                </DialogOverlay>
            </DialogTrigger>
        </div>
    );
};

Scrollable Content

When content exceeds the viewport, the DialogBody automatically becomes scrollable while the header and footer remain fixed.

"use client";
import { Button } from "@repo/ui/components/button";
import {
    DialogTrigger,
    DialogOverlay,
    DialogContent,
    DialogHeader,
    DialogTitle,
    DialogDescription,
    DialogBody,
    DialogFooter,
    DialogClose,
} from "@repo/ui/components/dialog";

/* SCROLLABLE CONTENT EXAMPLE */
export const Example5 = () => {
    return (
        <DialogTrigger>
            <Button variant="outline">Terms of Service</Button>
            <DialogOverlay>
                <DialogContent>
                    <DialogClose />
                    <DialogHeader>
                        <DialogTitle>Terms of Service</DialogTitle>
                        <DialogDescription>
                            Please read our terms of service carefully before
                            proceeding.
                        </DialogDescription>
                    </DialogHeader>
                    <DialogBody>
                        <div className="text-muted-foreground space-y-4 text-sm">
                            <div>
                                <h4 className="text-foreground mb-1 font-semibold">
                                    1. Acceptance of Terms
                                </h4>
                                <p>
                                    By accessing and using this service, you
                                    accept and agree to be bound by the terms
                                    and provisions of this agreement. If you do
                                    not agree to abide by the above, please do
                                    not use this service.
                                </p>
                            </div>
                            <div>
                                <h4 className="text-foreground mb-1 font-semibold">
                                    2. Use License
                                </h4>
                                <p>
                                    Permission is granted to temporarily use the
                                    materials on this website for personal,
                                    non-commercial transitory viewing only. This
                                    is the grant of a license, not a transfer of
                                    title.
                                </p>
                            </div>
                            <div>
                                <h4 className="text-foreground mb-1 font-semibold">
                                    3. Disclaimer
                                </h4>
                                <p>
                                    The materials on this website are provided
                                    on an &quot;as is&quot; basis. We make no
                                    warranties, expressed or implied, and hereby
                                    disclaim and negate all other warranties
                                    including, without limitation, implied
                                    warranties or conditions of merchantability.
                                </p>
                            </div>
                            <div>
                                <h4 className="text-foreground mb-1 font-semibold">
                                    4. Limitations
                                </h4>
                                <p>
                                    In no event shall the company or its
                                    suppliers be liable for any damages
                                    (including, without limitation, damages for
                                    loss of data or profit) arising out of the
                                    use or inability to use the materials on
                                    this website.
                                </p>
                            </div>
                            <div>
                                <h4 className="text-foreground mb-1 font-semibold">
                                    5. Accuracy of Materials
                                </h4>
                                <p>
                                    The materials appearing on this website
                                    could include technical, typographical, or
                                    photographic errors. The company does not
                                    warrant that any of the materials on its
                                    website are accurate, complete or current.
                                </p>
                            </div>
                            <div>
                                <h4 className="text-foreground mb-1 font-semibold">
                                    6. Links
                                </h4>
                                <p>
                                    The company has not reviewed all of the
                                    sites linked to its website and is not
                                    responsible for the contents of any such
                                    linked site. The inclusion of any link does
                                    not imply endorsement by the company of the
                                    site.
                                </p>
                            </div>
                            <div>
                                <h4 className="text-foreground mb-1 font-semibold">
                                    7. Modifications
                                </h4>
                                <p>
                                    The company may revise these terms of
                                    service at any time without notice. By using
                                    this website you are agreeing to be bound by
                                    the then current version of these terms of
                                    service.
                                </p>
                            </div>
                        </div>
                    </DialogBody>
                    <DialogFooter>
                        <DialogClose>
                            <Button variant="outline">Decline</Button>
                        </DialogClose>
                        <DialogClose>
                            <Button>Accept</Button>
                        </DialogClose>
                    </DialogFooter>
                </DialogContent>
            </DialogOverlay>
        </DialogTrigger>
    );
};

Alert Dialog

Set role="alertdialog" and isDismissable={false} on the overlay to create a non-dismissable alert that requires explicit user action.

"use client";
import { Button } from "@repo/ui/components/button";
import {
    DialogTrigger,
    DialogOverlay,
    DialogContent,
    DialogHeader,
    DialogTitle,
    DialogDescription,
    DialogFooter,
    DialogClose,
} from "@repo/ui/components/dialog";

/* ALERT DIALOG EXAMPLE */
export const Example6 = () => {
    return (
        <DialogTrigger>
            <Button variant="outline">Show Alert</Button>
            <DialogOverlay isDismissable={false}>
                <DialogContent role="alertdialog" size="sm">
                    <DialogHeader>
                        <DialogTitle>Session Expired</DialogTitle>
                        <DialogDescription>
                            Your session has expired due to inactivity. Please
                            sign in again to continue using the application.
                        </DialogDescription>
                    </DialogHeader>
                    <DialogFooter>
                        <DialogClose>
                            <Button>Sign In Again</Button>
                        </DialogClose>
                    </DialogFooter>
                </DialogContent>
            </DialogOverlay>
        </DialogTrigger>
    );
};

Nested Dialog

Dialogs can be nested — opening a second dialog from within the first. Each dialog manages its own focus trap independently.

"use client";
import { Button } from "@repo/ui/components/button";
import {
    DialogTrigger,
    DialogOverlay,
    DialogContent,
    DialogHeader,
    DialogTitle,
    DialogDescription,
    DialogBody,
    DialogFooter,
    DialogClose,
} from "@repo/ui/components/dialog";

/* NESTED DIALOG EXAMPLE */
export const Example7 = () => {
    return (
        <DialogTrigger>
            <Button variant="outline">Open Settings</Button>
            <DialogOverlay>
                <DialogContent>
                    <DialogClose />
                    <DialogHeader>
                        <DialogTitle>Settings</DialogTitle>
                        <DialogDescription>
                            Manage your application settings. Some actions may
                            require additional confirmation.
                        </DialogDescription>
                    </DialogHeader>
                    <DialogBody>
                        <div className="space-y-3">
                            <div className="flex items-center justify-between rounded-md border p-3">
                                <div>
                                    <p className="text-sm font-medium">
                                        Notifications
                                    </p>
                                    <p className="text-muted-foreground text-xs">
                                        Enable push notifications
                                    </p>
                                </div>
                                <Button variant="outline" size="sm">
                                    Configure
                                </Button>
                            </div>
                            <div className="flex items-center justify-between rounded-md border p-3">
                                <div>
                                    <p className="text-sm font-medium">
                                        Privacy
                                    </p>
                                    <p className="text-muted-foreground text-xs">
                                        Manage your data and privacy
                                    </p>
                                </div>
                                <Button variant="outline" size="sm">
                                    Manage
                                </Button>
                            </div>
                            <div className="border-destructive/20 flex items-center justify-between rounded-md border p-3">
                                <div>
                                    <p className="text-destructive text-sm font-medium">
                                        Danger Zone
                                    </p>
                                    <p className="text-muted-foreground text-xs">
                                        Irreversible actions
                                    </p>
                                </div>
                                <DialogTrigger>
                                    <Button variant="destructive" size="sm">
                                        Delete
                                    </Button>
                                    <DialogOverlay>
                                        <DialogContent size="sm">
                                            <DialogHeader>
                                                <DialogTitle>
                                                    Confirm Deletion
                                                </DialogTitle>
                                                <DialogDescription>
                                                    This is a nested dialog. Are
                                                    you sure you want to proceed
                                                    with this destructive
                                                    action?
                                                </DialogDescription>
                                            </DialogHeader>
                                            <DialogFooter>
                                                <DialogClose>
                                                    <Button variant="outline">
                                                        Go Back
                                                    </Button>
                                                </DialogClose>
                                                <DialogClose>
                                                    <Button variant="destructive">
                                                        Confirm Delete
                                                    </Button>
                                                </DialogClose>
                                            </DialogFooter>
                                        </DialogContent>
                                    </DialogOverlay>
                                </DialogTrigger>
                            </div>
                        </div>
                    </DialogBody>
                    <DialogFooter>
                        <DialogClose>
                            <Button variant="outline">Close</Button>
                        </DialogClose>
                    </DialogFooter>
                </DialogContent>
            </DialogOverlay>
        </DialogTrigger>
    );
};

Component Code

"use client";

import * as React from "react";
import { XIcon } from "lucide-react";
import {
    DialogTrigger as AriaDialogTrigger,
    Modal as AriaModal,
    ModalOverlay as AriaModalOverlay,
    Dialog as AriaDialog,
    Heading as AriaHeading,
    Button as AriaButton,
    composeRenderProps,
    type DialogTriggerProps as AriaDialogTriggerProps,
    type ModalOverlayProps as AriaModalOverlayProps,
    type DialogProps as AriaDialogProps,
    type HeadingProps as AriaHeadingProps,
    type ButtonProps as AriaButtonProps,
} from "react-aria-components";
import { cn, tv, type VariantProps } from "tailwind-variants";

/* -----------------------------------------------------------------------------
 * DialogTrigger (Root)
 * ---------------------------------------------------------------------------*/

interface DialogTriggerProps extends AriaDialogTriggerProps {}

export const DialogTrigger = (props: DialogTriggerProps) => {
    return <AriaDialogTrigger {...props} />;
};

/* -----------------------------------------------------------------------------
 * DialogOverlay
 * ---------------------------------------------------------------------------*/

const overlayStyles = tv({
    base: [
        "fixed inset-0 z-50 bg-black/50 backdrop-blur-sm",
        "flex items-center justify-center",
    ],
    variants: {
        isEntering: {
            true: "animate-in fade-in-0 duration-200",
        },
        isExiting: {
            true: "animate-out fade-out-0 duration-150",
        },
    },
});

interface DialogOverlayProps extends AriaModalOverlayProps {
    children: React.ReactNode;
}

export const DialogOverlay = ({
    className,
    children,
    isDismissable = true,
    ...props
}: DialogOverlayProps) => {
    return (
        <AriaModalOverlay
            data-slot="dialog-overlay"
            isDismissable={isDismissable}
            className={composeRenderProps(
                className,
                (className, renderProps) =>
                    cn(overlayStyles({ ...renderProps }), className) || "",
            )}
            {...props}
        >
            <AriaModal data-slot="dialog-modal" className="outline-none">
                {children}
            </AriaModal>
        </AriaModalOverlay>
    );
};

/* -----------------------------------------------------------------------------
 * DialogContent
 * ---------------------------------------------------------------------------*/

const contentStyles = tv({
    base: [
        "bg-background text-foreground relative flex flex-col rounded-lg border shadow-lg outline-none",
        "max-h-[85vh] w-full",
    ],
    variants: {
        size: {
            sm: "max-w-sm",
            default: "max-w-lg",
            lg: "max-w-2xl",
            full: "h-[calc(100vh-2rem)] max-w-[calc(100vw-2rem)]",
        },
        isEntering: {
            true: "animate-in fade-in-0 zoom-in-95 slide-in-from-bottom-2 duration-200",
        },
        isExiting: {
            true: "animate-out fade-out-0 zoom-out-95 slide-out-to-bottom-2 duration-150",
        },
    },
    defaultVariants: {
        size: "default",
    },
});

interface DialogContentProps
    extends AriaDialogProps,
        VariantProps<typeof contentStyles> {}

export const DialogContent = ({
    className,
    size,
    children,
    ...props
}: DialogContentProps) => {
    return (
        <AriaDialog
            data-slot="dialog-content"
            className={cn(contentStyles({ size }), className)}
            {...props}
        >
            {children}
        </AriaDialog>
    );
};

/* -----------------------------------------------------------------------------
 * DialogHeader
 * ---------------------------------------------------------------------------*/

interface DialogHeaderProps extends React.ComponentProps<"div"> {}

export const DialogHeader = ({
    className,
    children,
    ...props
}: DialogHeaderProps) => {
    return (
        <div
            data-slot="dialog-header"
            className={cn("flex flex-col gap-1.5 p-6 pb-0", className)}
            {...props}
        >
            {children}
        </div>
    );
};

/* -----------------------------------------------------------------------------
 * DialogTitle
 * ---------------------------------------------------------------------------*/

interface DialogTitleProps extends AriaHeadingProps {}

export const DialogTitle = ({ className, ...props }: DialogTitleProps) => {
    return (
        <AriaHeading
            data-slot="dialog-title"
            slot="title"
            className={cn(
                "text-lg font-semibold leading-none tracking-tight",
                className,
            )}
            {...props}
        />
    );
};

/* -----------------------------------------------------------------------------
 * DialogDescription
 * ---------------------------------------------------------------------------*/

interface DialogDescriptionProps extends React.ComponentProps<"p"> {}

export const DialogDescription = ({
    className,
    ...props
}: DialogDescriptionProps) => {
    return (
        <p
            data-slot="dialog-description"
            className={cn("text-muted-foreground text-sm", className)}
            {...props}
        />
    );
};

/* -----------------------------------------------------------------------------
 * DialogBody
 * ---------------------------------------------------------------------------*/

interface DialogBodyProps extends React.ComponentProps<"div"> {}

export const DialogBody = ({
    className,
    children,
    ...props
}: DialogBodyProps) => {
    return (
        <div
            data-slot="dialog-body"
            className={cn("flex-1 overflow-y-auto p-6", className)}
            {...props}
        >
            {children}
        </div>
    );
};

/* -----------------------------------------------------------------------------
 * DialogFooter
 * ---------------------------------------------------------------------------*/

interface DialogFooterProps extends React.ComponentProps<"div"> {}

export const DialogFooter = ({
    className,
    children,
    ...props
}: DialogFooterProps) => {
    return (
        <div
            data-slot="dialog-footer"
            className={cn(
                "flex flex-col-reverse gap-2 p-6 pt-0 sm:flex-row sm:justify-end",
                className,
            )}
            {...props}
        >
            {children}
        </div>
    );
};

/* -----------------------------------------------------------------------------
 * DialogClose
 * ---------------------------------------------------------------------------*/

interface DialogCloseProps extends AriaButtonProps {}

export const DialogClose = ({
    className,
    children,
    ...props
}: DialogCloseProps) => {
    if (children) {
        return (
            <AriaButton
                data-slot="dialog-close"
                slot="close"
                className={cn(className)}
                {...props}
            >
                {children}
            </AriaButton>
        );
    }

    return (
        <AriaButton
            data-slot="dialog-close"
            slot="close"
            className={cn(
                "ring-offset-background focus:ring-ring absolute right-4 top-4 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:pointer-events-none",
                className,
            )}
            {...props}
        >
            <XIcon className="size-4" />
            <span className="sr-only">Close</span>
        </AriaButton>
    );
};