A reusable modal dialog with backdrop, keyboard handling, and portal rendering.
import { useEffect, type ReactNode } from class=class="text-emerald-400">"text-emerald-400">'react'
import { createPortal } from class=class="text-emerald-400">"text-emerald-400">'react-dom'
interface ModalProps {
isOpen: boolean
onClose: () => void
children: ReactNode
title?: string
}
export function Modal({ isOpen, onClose, children, title }: ModalProps) {
useEffect(() => {
const handleEsc = (e: KeyboardEvent) => {
if (e.key === class=class="text-emerald-400">"text-emerald-400">'Escape') onClose()
}
if (isOpen) {
document.addEventListener(class=class="text-emerald-400">"text-emerald-400">'keydown', handleEsc)
document.body.style.overflow = class=class="text-emerald-400">"text-emerald-400">'hidden'
}
return () => {
document.removeEventListener(class=class="text-emerald-400">"text-emerald-400">'keydown', handleEsc)
document.body.style.overflow = class=class="text-emerald-400">"text-emerald-400">''
}
}, [isOpen, onClose])
if (!isOpen) return null
return createPortal(
<div className=class="text-emerald-400">"fixed inset-0 z-50 flex items-center justify-center">
<div className=class="text-emerald-400">"absolute inset-0 bg-black/50" onClick={onClose} />
<div className=class="text-emerald-400">"relative bg-white rounded-lg p-6 max-w-md w-full mx-4 shadow-xl">
{title && <h2 className=class="text-emerald-400">"text-lg font-semibold mb-4">{title}</h2>}
{children}
</div>
</div>,
document.body
)
}Control visibility with state: <Modal isOpen={open} onClose={() => setOpen(false)} title="Confirm">Content here</Modal>. It closes on Escape key, locks body scroll, and renders via a portal.
Let's discuss how we can bring your idea to life. From initial concept to production-ready product — we've got you covered.