Agents (llms.txt)
Octocat

Dialog

Dialog allows you to create modal elements that overlay the main content.

import { Button } from '@/components/button';
import { Dialog } from '@/components/dialog';

export default function DialogPreview() {
  return (
    <Dialog>
      <Dialog.Trigger asChild>
        <Button variant="outline">Open</Button>
      </Dialog.Trigger>
      <Dialog.Content className="w-80">
        <Dialog.Title>Unsaved changes</Dialog.Title>
        <Dialog.Description>
          Are you sure you want to leave this page?
        </Dialog.Description>
        <Dialog.Actions>
          <Dialog.Close asChild>
            <Button>Confirm</Button>
          </Dialog.Close>
          <Dialog.Close asChild>
            <Button variant="outline">Cancel</Button>
          </Dialog.Close>
        </Dialog.Actions>
      </Dialog.Content>
    </Dialog>
  );
}

Dependencies

Source Code

import { Modal } 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 Modal.Content> {
  align?: 'center' | 'top';
}

const DialogContent = ({
  className,
  children,
  align = 'center',
  ...props
}: DialogContentProps) => {
  return (
    <Modal.Content
      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}
    </Modal.Content>
  );
};

const DialogTrigger = Modal.Trigger;

const DialogClose = Modal.Close;

const DialogTitle = ({
  children,
  className,
  ...props
}: React.ComponentProps<typeof Modal.Title>) => {
  return (
    <Modal.Title className={cn('pb-2 font-semibold', className)} {...props}>
      {children}
    </Modal.Title>
  );
};

const DialogDescription = ({
  children,
  className,
  ...props
}: React.ComponentProps<typeof Modal.Description>) => {
  return (
    <Modal.Description className={cn('pb-2', className)} {...props}>
      {children}
    </Modal.Description>
  );
};

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>
);

const CompoundDialog = Object.assign(Dialog, {
  Content: DialogContent,
  Title: DialogTitle,
  Description: DialogDescription,
  Actions: DialogActions,
  Trigger: DialogTrigger,
  Close: DialogClose,
});

export { CompoundDialog as Dialog };

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>
  <Dialog.Trigger />
  <Dialog.Content>
    <Dialog.Title />
    <Dialog.Close />
    <Dialog.Description />
    <Dialog.Actions />
  </Dialog.Content>
</Dialog>
        

API Reference

Dialog

Extends the Modal component.

Dialog.Trigger

Extends the ModalTrigger component.

Dialog.Content

Extends the ModalContent component.

Prop Default Type Description
align "center" "center""top" The vertical alignment of the dialog.

Dialog.Title

Extends the ModalTitle component.

Dialog.Description

Extends the ModalDescription component.

Dialog.Actions

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”.

Dialog.Close

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.

import { Button } from '@/components/button';
import { Dialog } from '@/components/dialog';

export default function DialogPreview() {
  return (
    <Dialog>
      <Dialog.Trigger asChild>
        <Button variant="outline">Open</Button>
      </Dialog.Trigger>
      <Dialog.Content className="w-80">
        <Dialog.Title>Unsaved changes</Dialog.Title>
        <Dialog.Description>
          Are you sure you want to leave this page?
        </Dialog.Description>
        <Dialog.Actions>
          <Dialog.Close asChild>
            <Button>Confirm</Button>
          </Dialog.Close>
          <Dialog.Close asChild>
            <Button variant="outline">Cancel</Button>
          </Dialog.Close>
        </Dialog.Actions>
      </Dialog.Content>
    </Dialog>
  );
}

Align to the top

Dialog aligned to the top of the screen.

import { Button } from '@/components/button';
import { Dialog } from '@/components/dialog';

export default function DialogTopPreview() {
  return (
    <Dialog>
      <Dialog.Trigger asChild>
        <Button variant="outline">Open at the top</Button>
      </Dialog.Trigger>
      <Dialog.Content align="top">
        <Dialog.Title>Unsaved changes</Dialog.Title>
        <Dialog.Description>
          Are you sure you want to leave this page?
        </Dialog.Description>
        <Dialog.Actions>
          <Button>Confirm</Button>
          <Dialog.Close asChild>
            <Button variant="outline">Cancel</Button>
          </Dialog.Close>
        </Dialog.Actions>
      </Dialog.Content>
    </Dialog>
  );
}

Destructive

A dialog for destructive actions with appropriate styling.

import { Button } from '@/components/button';
import { Dialog } from '@/components/dialog';

export default function DialogDestructivePreview() {
  return (
    <Dialog>
      <Dialog.Trigger asChild>
        <Button variant="destructive">Delete</Button>
      </Dialog.Trigger>
      <Dialog.Content className="w-80">
        <Dialog.Title>Are you sure?</Dialog.Title>
        <Dialog.Description>
          This action cannot be undone. This will permanently delete your
          account and remove your data from our servers.
        </Dialog.Description>
        <Dialog.Actions>
          <Button variant="destructive">Delete everything</Button>
          <Dialog.Close asChild>
            <Button variant="outline">Cancel</Button>
          </Dialog.Close>
        </Dialog.Actions>
      </Dialog.Content>
    </Dialog>
  );
}

Tall content

Dialog with scrollable content.

import { Button } from '@/components/button';
import { Dialog } from '@/components/dialog';

export default function DialogTallPreview() {
  return (
    <Dialog>
      <Dialog.Trigger asChild>
        <Button variant="outline">Open Tall Dialog</Button>
      </Dialog.Trigger>
      <Dialog.Content>
        <Dialog.Title>Very Tall Dialog</Dialog.Title>
        <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>
        <Dialog.Actions>
          <Dialog.Close asChild>
            <Button variant="outline">Close</Button>
          </Dialog.Close>
        </Dialog.Actions>
      </Dialog.Content>
    </Dialog>
  );
}

Arbitrary content

Dialog with custom content layout.

import { XIcon } from '@phosphor-icons/react/dist/ssr';

import { Button, IconButton } from '@/components/button';
import { Dialog } from '@/components/dialog';

export default function DialogArbitraryPreview() {
  return (
    <Dialog>
      <Dialog.Trigger asChild>
        <Button variant="outline">Open Dialog</Button>
      </Dialog.Trigger>
      <Dialog.Content className="p-0">
        <Dialog.Close className="absolute top-3 right-3" asChild>
          <IconButton variant="outline" size="sm" aria-label="Close dialog">
            <XIcon className="size-4" />
          </IconButton>
        </Dialog.Close>
        <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>
      </Dialog.Content>
    </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