We use tracking cookies to understand how you use the product and help us improve it. For more information on how we store cookies, read our  privacy policy.

Dialog.tsx

Modal dialog component built with Radix UI for accessible overlays

The Dialog component is a fully accessible modal dialog built on Radix UI primitives with animations and customizable content.

Features

  • Accessible modal with focus management
  • Backdrop overlay with blur effect
  • Smooth open/close animations
  • Optional close button
  • Keyboard navigation (Esc to close)
  • Portal rendering for proper stacking

Basic Usage

app/page.tsx
'use client';

import { Dialog, DialogTrigger, DialogContent, DialogHeader, DialogTitle, DialogDescription } from '@/components/ui/Dialog';
import { Button } from '@/components/ui/Button';

export default function Page() {
  return (
    <Dialog>
      <DialogTrigger asChild>
        <Button>Open Dialog</Button>
      </DialogTrigger>
      <DialogContent>
        <DialogHeader>
          <DialogTitle>Dialog Title</DialogTitle>
          <DialogDescription>
            This is a description of the dialog content.
          </DialogDescription>
        </DialogHeader>
        <p>Dialog content goes here.</p>
      </DialogContent>
    </Dialog>
  );
}
app/page.tsx
'use client';

import { Dialog, DialogTrigger, DialogContent, DialogHeader, DialogTitle, DialogFooter } from '@/components/ui/Dialog';
import { Button } from '@/components/ui/Button';

<Dialog>
  <DialogTrigger asChild>
    <Button>Delete Account</Button>
  </DialogTrigger>
  <DialogContent>
    <DialogHeader>
      <DialogTitle>Are you sure?</DialogTitle>
    </DialogHeader>
    <p>This action cannot be undone.</p>
    <DialogFooter>
      <Button variant="outline">Cancel</Button>
      <Button variant="destructive">Delete</Button>
    </DialogFooter>
  </DialogContent>
</Dialog>

Without Close Button

app/page.tsx
<DialogContent showCloseButton={false}>
  <DialogHeader>
    <DialogTitle>No Close Button</DialogTitle>
  </DialogHeader>
  <p>User must click an action button to close.</p>
  <DialogFooter>
    <Button>Close</Button>
  </DialogFooter>
</DialogContent>

Controlled Dialog

app/page.tsx
'use client';

import { useState } from 'react';
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/Dialog';
import { Button } from '@/components/ui/Button';

export default function Page() {
  const [open, setOpen] = useState(false);

  return (
    <>
      <Button onClick={() => setOpen(true)}>Open</Button>
      
      <Dialog open={open} onOpenChange={setOpen}>
        <DialogContent>
          <DialogHeader>
            <DialogTitle>Controlled Dialog</DialogTitle>
          </DialogHeader>
          <p>This dialog is controlled by state.</p>
          <Button onClick={() => setOpen(false)}>Close</Button>
        </DialogContent>
      </Dialog>
    </>
  );
}

Custom Styling

app/page.tsx
<DialogContent className="max-w-3xl">
  <DialogHeader>
    <DialogTitle className="text-2xl">Large Dialog</DialogTitle>
  </DialogHeader>
  <div className="space-y-4">
    <p>Custom styled dialog with larger width.</p>
  </div>
</DialogContent>

With Form

app/page.tsx
'use client';

import { Dialog, DialogTrigger, DialogContent, DialogHeader, DialogTitle, DialogFooter } from '@/components/ui/Dialog';
import { Button } from '@/components/ui/Button';
import { Input } from '@/components/ui/Input';
import { Label } from '@/components/ui/Label';

<Dialog>
  <DialogTrigger asChild>
    <Button>Edit Profile</Button>
  </DialogTrigger>
  <DialogContent>
    <DialogHeader>
      <DialogTitle>Edit Profile</DialogTitle>
    </DialogHeader>
    <form className="space-y-4">
      <div>
        <Label htmlFor="name">Name</Label>
        <Input id="name" placeholder="Enter your name" />
      </div>
      <div>
        <Label htmlFor="email">Email</Label>
        <Input id="email" type="email" placeholder="Enter your email" />
      </div>
      <DialogFooter>
        <Button type="submit">Save Changes</Button>
      </DialogFooter>
    </form>
  </DialogContent>
</Dialog>

Implementation

The Dialog is built with Radix UI primitives:

components/ui/Dialog.tsx
'use client';

import * as DialogPrimitive from '@radix-ui/react-dialog';

function Dialog({ ...props }: React.ComponentProps<typeof DialogPrimitive.Root>) {
  return <DialogPrimitive.Root {...props} />;
}

function DialogContent({
  className,
  children,
  showCloseButton = true,
  ...props
}: React.ComponentProps<typeof DialogPrimitive.Content> & {
  showCloseButton?: boolean;
}) {
  return (
    <DialogPortal>
      <DialogOverlay />
      <DialogPrimitive.Content
        className={cn(
          'bg-glass-surface backdrop-blur-lg fixed top-[50%] left-[50%] z-50 translate-x-[-50%] translate-y-[-50%] rounded-lg border p-6',
          className
        )}
        {...props}
      >
        {children}
        {showCloseButton && (
          <DialogPrimitive.Close className="absolute top-4 right-4">
            <XIcon />
          </DialogPrimitive.Close>
        )}
      </DialogPrimitive.Content>
    </DialogPortal>
  );
}

Components

Dialog

Root component that manages dialog state.

DialogTrigger

Button or element that opens the dialog.

DialogContent

Main dialog content container with backdrop and animations.

DialogHeader

Header section for title and description.

DialogTitle

Dialog title (required for accessibility).

DialogDescription

Optional description text.

DialogFooter

Footer section for action buttons.

DialogClose

Programmatic close trigger.

Accessibility

  • Focus trapped within dialog when open
  • Esc key closes dialog
  • Click outside closes dialog
  • Focus returns to trigger on close
  • Proper ARIA attributes
  • Screen reader announcements

Animations

  • Fade in/out for overlay
  • Zoom in/out for content
  • Smooth transitions with Tailwind animations

How is this guide ?

Last updated on