Modal
An accessible dialog built on the native HTML dialog element — closes on Escape, backdrop click, or the × button.
What it does
Modal wraps the browser's native <dialog> element. It uses showModal() and close() directly, which means focus is trapped inside automatically, Escape closes it, and the backdrop is handled by the browser — no extra libraries needed.
The modal syncs open/closed state via the isOpen prop. Your parent component owns the state and passes the setter down as setIsOpen.
Props
isOpen— boolean. Whentruethe dialog opens; whenfalseit closes. Required.setIsOpen— a(open: boolean) => voidsetter. The modal calls it withfalsewhen the user dismisses — via Escape, the backdrop, or the × button. Required.title— optional string rendered as a heading inside the modal. Leave it out if you want to control the heading yourself viachildren.children— any JSX rendered inside the modal body. Required.
Usage
"use client";
import { useState } from "react";
import Modal from "@/components/Modal";
export default function Example() {
const [open, setOpen] = useState(false);
return (
<>
<button onClick={() => setOpen(true)}>Open modal</button>
<Modal isOpen={open} setIsOpen={setOpen} title="Confirm action">
<p>Are you sure you want to continue?</p>
<div className="mt-4 flex justify-end gap-2">
<button onClick={() => setOpen(false)}>Cancel</button>
<button onClick={handleConfirm}>Confirm</button>
</div>
</Modal>
</>
);
}Styling
The dialog is constrained to max-w-lg and centered with m-auto. To change the width, wrap the modal content in a <div> with a custom width class, or override the dialog size directly in src/components/Modal.tsx. The backdrop uses a semi-transparent neutral overlay — adjust the backdrop:bg-neutral/50 class on the <dialog> element to change it.