Basic Modal
A titled dialog with a focus trap, Escape to close and focus returned to the trigger.
6 frameworksIntermediate
A consent dialog whose long body scrolls between a fixed header and Accept/Decline footer.
<!--
The panel is a flex column with a fixed frame: a flex-none header and footer
bracket a flex-1 min-h-0 overflow-y-auto body. min-h-0 is the load-bearing
line - without it the flex child refuses to shrink and the body pushes the
Accept/Decline row off the panel instead of scrolling under it. The way out
stays reachable however far you read.
-->
<div class="fixed inset-0 z-50 flex items-center justify-center p-4">
<div class="absolute inset-0 bg-black/50" data-modal-close></div>
<div
role="dialog"
aria-modal="true"
aria-labelledby="terms-modal-title"
class="relative flex max-h-[calc(100dvh-2rem)] w-full max-w-lg flex-col overflow-hidden rounded-2xl border border-gray-200 bg-white shadow-2xl dark:border-gray-800 dark:bg-gray-900"
>
<div class="flex-none border-b border-gray-200 px-6 py-4 dark:border-gray-800">
<h2 id="terms-modal-title" class="text-lg font-semibold text-gray-900 dark:text-gray-100">Terms of Service</h2>
</div>
<div class="min-h-0 flex-1 space-y-3 overflow-y-auto px-6 py-4 text-sm leading-relaxed text-gray-600 dark:text-gray-400">
<p>By using this service you agree to the terms below. Please read them in full before continuing.</p>
<p>1. Accounts. You are responsible for keeping your credentials secure.</p>
<p>2. Acceptable use. You agree not to misuse the service or interfere with its operation.</p>
<p>3. Content. You retain ownership of the content you submit.</p>
<p>4. Privacy. We process personal data as described in our Privacy Policy.</p>
<p>5. Termination. You may stop using the service at any time.</p>
</div>
<div class="flex-none border-t border-gray-200 px-6 py-4 dark:border-gray-800">
<div class="flex flex-col gap-2 sm:flex-row sm:justify-end">
<button type="button" data-modal-close class="w-full rounded-lg border border-gray-300 bg-white px-4 py-2 text-sm font-semibold text-gray-700 hover:bg-gray-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-600 focus-visible:ring-offset-2 focus-visible:ring-offset-white sm:w-auto dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 dark:hover:bg-gray-800 dark:focus-visible:ring-blue-400 dark:focus-visible:ring-offset-gray-900">Decline</button>
<button type="button" data-modal-accept class="w-full rounded-lg bg-blue-600 px-4 py-2 text-sm font-semibold text-white hover:bg-blue-700 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-600 focus-visible:ring-offset-2 focus-visible:ring-offset-white sm:w-auto dark:focus-visible:ring-blue-400 dark:focus-visible:ring-offset-gray-900">Accept</button>
</div>
</div>
</div>
</div>| Prop | Type | Default | Description |
|---|---|---|---|
openrequired | boolean | - | Whether the component is currently shown. |
titlerequired | string | - | Heading text for the card. |
childrenrequired | ReactNode | - | Content rendered inside the component. |
acceptLabel | string | 'Accept' | Accept label |
declineLabel | string | 'Decline' | Decline label |
onAcceptrequired | () => void | - | On accept |
onDismissrequired | () => void | - | Fired when the user dismisses the notification. |
The panel is a flex column: a `flex-none` header and footer bracket a `flex-1 min-h-0 overflow-y-auto` body. `min-h-0` is the load-bearing line - without it the flex child refuses to shrink and the body pushes the Accept/Decline row off the panel instead of scrolling under it. The way out stays reachable however far the reader scrolls, and focus opens on Decline, the safe choice.