Modal window with header, footer, title and description, built on Base UI Dialog.
Installation
pnpm dlx shadcn@latest add https://rdf.dev.360online.com/r/dialog.jsonInstalls to @/components/ui/dialog.
Manual installation
Install the dependencies:
pnpm add @base-ui/react@^1.8.0 lucide-react@^1.48.0Add the components it builds on: button.
Copy the source into your project:
'use client'
import { Dialog as DialogPrimitive } from '@base-ui/react/dialog'
import { XIcon } from 'lucide-react'
import * as React from 'react'
import { Button } from '@/components/ui/button'
import { cn } from '@/lib/utils'
function Dialog({ ...props }: DialogPrimitive.Root.Props) {
return <DialogPrimitive.Root data-slot="dialog" {...props} />
}
function DialogTrigger({ ...props }: DialogPrimitive.Trigger.Props) {
return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />
}
function DialogPortal({ ...props }: DialogPrimitive.Portal.Props) {
return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />
}
function DialogClose({ ...props }: DialogPrimitive.Close.Props) {
return <DialogPrimitive.Close data-slot="dialog-close" {...props} />
}
function DialogOverlay({ className, ...props }: DialogPrimitive.Backdrop.Props) {
return (
<DialogPrimitive.Backdrop
data-slot="dialog-overlay"
className={cn(
'fixed inset-0 isolate z-50 bg-black/50 duration-100 supports-backdrop-filter:backdrop-blur-xs data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0',
className,
)}
{...props}
/>
)
}
function DialogContent({
className,
children,
showCloseButton = true,
...props
}: DialogPrimitive.Popup.Props & {
showCloseButton?: boolean
}) {
return (
<DialogPortal>
<DialogOverlay />
<DialogPrimitive.Popup
data-slot="dialog-content"
className={cn(
'fixed top-1/2 left-1/2 z-50 grid max-h-[calc(100dvh-2rem)] w-full max-w-[calc(100%-2rem)] -translate-x-1/2 -translate-y-1/2 gap-4 overflow-y-auto rounded-xl bg-background p-4 text-sm text-foreground ring-1 ring-border duration-100 outline-none sm:max-w-sm data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95',
className,
)}
{...props}
>
{children}
{showCloseButton && (
<DialogPrimitive.Close
data-slot="dialog-close"
render={
<Button
variant="ghost"
className="absolute top-2 right-2 text-muted-foreground active:bg-muted/70"
size="icon-sm"
/>
}
>
<XIcon />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
)}
</DialogPrimitive.Popup>
</DialogPortal>
)
}
function DialogHeader({ className, ...props }: React.ComponentProps<'div'>) {
return <div data-slot="dialog-header" className={cn('flex flex-col gap-2', className)} {...props} />
}
function DialogFooter({
className,
showCloseButton = false,
children,
...props
}: React.ComponentProps<'div'> & {
showCloseButton?: boolean
}) {
return (
<div
data-slot="dialog-footer"
className={cn(
'-mx-4 -mb-4 flex flex-col-reverse gap-2 rounded-b-xl border-t bg-muted/50 p-4 sm:flex-row sm:justify-end',
className,
)}
{...props}
>
{children}
{showCloseButton && <DialogPrimitive.Close render={<Button variant="outline" />}>Close</DialogPrimitive.Close>}
</div>
)
}
function DialogTitle({ className, ...props }: DialogPrimitive.Title.Props) {
return (
<DialogPrimitive.Title
data-slot="dialog-title"
className={cn('font-heading text-base leading-none font-medium', className)}
{...props}
/>
)
}
function DialogDescription({ className, ...props }: DialogPrimitive.Description.Props) {
return (
<DialogPrimitive.Description
data-slot="dialog-description"
className={cn(
'text-sm text-muted-foreground *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground',
className,
)}
{...props}
/>
)
}
export {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogOverlay,
DialogPortal,
DialogTitle,
DialogTrigger,
}A modal dialog component that overlays content on top of the main view. Built on top of Base UI Dialog, it follows WAI-ARIA design patterns and provides a fully accessible dialog experience.
Features
- Focus management
- Keyboard navigation
- Screen reader support
- Responsive design
- Customizable content
- Animated transitions
- Blurred backdrop overlay
- Built-in close button (toggle with
showCloseButtononDialogContent) - Optional footer close button (
showCloseButtononDialogFooter) - Mobile-friendly
- ARIA compliant
Usage
import {
Dialog,
DialogTrigger,
DialogContent,
DialogHeader,
DialogFooter,
DialogTitle,
DialogDescription,
DialogClose,
} from '@/components/ui/dialog'
import { Button } from '@/components/ui/button'
import { Field, FieldLabel } from '@/components/ui/field'
import { Input } from '@/components/ui/input'
// Basic usage
export function BasicDialog() {
return (
<Dialog>
<DialogTrigger render={<Button />}>Edit contact details</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Contact details</DialogTitle>
<DialogDescription>
We use these details to reach you about your cases.
</DialogDescription>
</DialogHeader>
<Field>
<FieldLabel htmlFor="email">Email</FieldLabel>
<Input id="email" type="email" defaultValue="anna.lindqvist@example.se" />
</Field>
<DialogFooter>
<DialogClose render={<Button variant="outline" />}>Cancel</DialogClose>
<Button>Save changes</Button>
</DialogFooter>
</DialogContent>
</Dialog>
)
}
// Controlled dialog
export function ControlledDialog() {
const [open, setOpen] = React.useState(false)
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger render={<Button />}>Send supplement</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Send supplement to case 2026-04812?</DialogTitle>
</DialogHeader>
<DialogFooter>
<Button variant="outline" onClick={() => setOpen(false)}>
Cancel
</Button>
<Button onClick={() => { sendSupplement(); setOpen(false) }}>
Send
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
)
}Examples
Basic Dialog
A form in a dialog for editing a resident's contact details.
Controlled Dialog
A confirmation that closes itself after sending, using the controlled open state. The page below the button updates when the files are sent.
With Custom Content
A case summary with a definition list, a separator and the case officer's contact details.
Footer Close Button
The corner close button is hidden with showCloseButton={false} on DialogContent, and DialogFooter renders an outline "Close" button with showCloseButton. Here it closes a dialog for sharing a public agenda.
Scrollable Content
A privacy notice in a scrollable region, with the confirmation and footer always in view.
Props
Dialog
defaultOpen- The open state when initially renderedopen- The controlled open stateonOpenChange- Event handler for open state changesmodal- Whether the dialog is modal (blocks interactions outside)
DialogTrigger
render- Element to render instead of the default<button>, e.g.render={<Button variant="outline" />}children- The trigger's content
DialogContent
children- Content to display in the dialogclassName- Additional CSS classes (default max width issm:max-w-sm; override with e.g.sm:max-w-lg)showCloseButton- Whether to render the close button in the top-right corner (defaulttrue)initialFocus/finalFocus- Element to focus when the dialog opens / closes
DialogClose
render- Element to render instead of the default<button>, e.g.render={<Button variant="outline" />}- Closes the dialog when clicked
DialogHeader
className- Additional CSS classeschildren- Header content
DialogFooter
className- Additional CSS classesshowCloseButton- Whether to append an outline "Close" button (defaultfalse)children- Footer content
DialogTitle
className- Additional CSS classeschildren- Title text
DialogDescription
className- Additional CSS classeschildren- Description text
Accessibility
The Dialog component follows WAI-ARIA guidelines:
- Uses
role="dialog"orrole="alertdialog" - Manages focus trap within dialog
- Supports keyboard navigation:
Esc: Close dialogTab: Navigate focusable elementsShift+Tab: Navigate focusable elements (reverse)
- Announces dialog title and description
- Provides clear action buttons
- Maintains focus on trigger after closing
Best Practices
-
Content Organization:
- Use clear, descriptive titles
- Keep content focused and concise
- Provide clear actions
- Group related information
-
Visual Design:
- Maintain proper spacing
- Use consistent styling
- Consider mobile layouts
- Ensure readable text
-
Interaction:
- Provide clear feedback
- Consider animations
- Manage focus properly
-
Accessibility:
- Write descriptive labels
- Use semantic markup
- Test with screen readers
- Verify keyboard navigation
Related Components
- Alert Dialog
- Sheet
- Drawer
- Popover
- Sheet