ComponentModalDialogOverlay
Modal Dialog
An accessible modal dialog component with backdrop, animations, and keyboard support. Uses React Portal for proper DOM placement.
Preview
Code
'use client'
import { useEffect, useRef } from 'react'
import { createPortal } from 'react-dom'
import { X } from 'lucide-react'
import { clsx } from 'clsx'
interface ModalProps {
isOpen: boolean
onClose: () => void
title?: string
children: React.ReactNode
}
export function Modal({ isOpen, onClose, title, children }: ModalProps) {
const overlayRef = useRef<HTMLDivElement>(null)
useEffect(() => {
const handleEscape = (e: KeyboardEvent) => {
if (e.key === 'Escape') onClose()
}
if (isOpen) {
document.addEventListener('keydown', handleEscape)
document.body.style.overflow = 'hidden'
}
return () => {
document.removeEventListener('keydown', handleEscape)
document.body.style.overflow = ''
}
}, [isOpen, onClose])
if (!isOpen) return null
return createPortal(
<div
ref={overlayRef}
onClick={(e) => e.target === overlayRef.current && onClose()}
className={clsx(
'fixed inset-0 z-50 flex items-center justify-center p-4',
'bg-black/50 backdrop-blur-sm',
'animate-in fade-in duration-200'
)}
>
<div className={clsx(
'w-full max-w-md rounded-xl bg-white shadow-xl dark:bg-olive-900',
'animate-in zoom-in-95 duration-200'
)}>
<div className="flex items-center justify-between border-b border-olive-100 px-6 py-4 dark:border-olive-800">
{title && (
<h2 className="font-display text-lg font-medium text-olive-950 dark:text-white">
{title}
</h2>
)}
<button
onClick={onClose}
className="rounded-lg p-1 text-olive-500 hover:bg-olive-100 dark:hover:bg-olive-800"
>
<X className="size-5" />
</button>
</div>
<div className="px-6 py-4">{children}</div>
</div>
</div>,
document.body
)
}Looking for more?
Browse the full collection of components or check out other exploration topics.