Octocat

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

  1. Content Structure:

    • Always include a clear title that describes the purpose
    • Keep content concise and focused
    • Use appropriate action labels (avoid “OK/Cancel”)
  2. Mobile Considerations:

    • Test on different screen sizes
    • Ensure touch targets are large enough
    • Consider native scroll behavior for tall content
  3. 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