A Modal / Dialog
Build a centered modal with a backdrop — CSS-only for demos, native <dialog> for production.
A modal is really three things: a trigger, a full-screen backdrop, and a centered panel. Tailwind makes each trivial; the interesting part is doing it accessibly.
The layout recipe
- Backdrop:
fixed inset-0 bg-black/50covers the viewport. - Centering: make the backdrop a
grid place-items-centerand drop the panel inside — perfectly centered, no transforms. - Panel:
bg-white rounded-2xl shadow-xl w-full max-w-md p-6.
A no-JavaScript version
For a demo, a hidden checkbox toggles everything through peer-checked:. Great for teaching, and it genuinely runs with zero script.
What production actually wants
The honest senior note: use the native<dialog>element. It gives you focus trapping,Escto close, a real backdrop, and inertness of the page behind — accessibility you should not hand-roll. Then styledialogand::backdropwith Tailwind and you get the best of both.
Example
When to use it
- A SaaS app opens a confirmation dialog before deleting a workspace, using a fixed backdrop and centered panel built with Tailwind so no CSS file needs to be touched.
- An image gallery shows a lightbox modal with a close button and keyboard-accessible native <dialog> element, styled with Tailwind utilities for the backdrop and panel.
- An onboarding flow presents a multi-step wizard inside a modal that blocks the page scroll with overflow-hidden on the body, all toggled from a single data attribute.
More examples
CSS-only backdrop and panel
fixed inset-0 covers the full viewport; bg-black/50 creates the semi-transparent backdrop; flex items-center justify-center centers the panel — all without a single line of custom CSS.
<div id="modal" class="hidden fixed inset-0 z-50 flex items-center justify-center bg-black/50">
<div class="bg-white rounded-2xl shadow-xl w-full max-w-md mx-4 p-6">
<h2 class="text-lg font-bold text-gray-900">Delete workspace?</h2>
<p class="mt-2 text-sm text-gray-600">This action cannot be undone.</p>
<div class="mt-6 flex justify-end gap-3">
<button onclick="document.getElementById('modal').classList.add('hidden')"
class="px-4 py-2 rounded-lg border border-gray-300 text-sm hover:bg-gray-50">Cancel</button>
<button class="px-4 py-2 rounded-lg bg-red-600 text-white text-sm font-semibold hover:bg-red-700">Delete</button>
</div>
</div>
</div>Native dialog element styled with Tailwind
The native <dialog> element handles focus trapping and the Escape key for free; backdrop: variant styles its built-in backdrop pseudo-element with Tailwind.
<dialog id="my-dialog"
class="rounded-2xl shadow-xl p-6 w-full max-w-md backdrop:bg-black/50">
<h2 class="text-lg font-bold text-gray-900">Confirm Action</h2>
<p class="mt-2 text-sm text-gray-600">Are you sure you want to proceed?</p>
<div class="mt-6 flex justify-end gap-3">
<button onclick="document.getElementById('my-dialog').close()"
class="px-4 py-2 rounded-lg border border-gray-300 text-sm hover:bg-gray-50">Cancel</button>
<button class="px-4 py-2 rounded-lg bg-cyan-600 text-white text-sm font-semibold hover:bg-cyan-700">Confirm</button>
</div>
</dialog>
<button onclick="document.getElementById('my-dialog').showModal()"
class="px-4 py-2 rounded-lg bg-cyan-600 text-white text-sm font-semibold">Open dialog</button>Animated modal with scroll lock
Adds a fade + scale-in animation via opacity and scale transition utilities, and locks body scroll with overflow-hidden on the body while the modal is open.
<div id="modal"
class="fixed inset-0 z-50 flex items-center justify-center bg-black/50
opacity-0 pointer-events-none transition-opacity duration-200">
<div id="modal-panel"
class="bg-white rounded-2xl shadow-xl w-full max-w-lg mx-4 p-8
scale-95 transition-transform duration-200">
<h2 class="text-xl font-bold text-gray-900">Welcome aboard!</h2>
<p class="mt-3 text-gray-600 text-sm">Complete your profile to get started.</p>
<button onclick="closeModal()"
class="mt-6 px-5 py-2 rounded-lg bg-cyan-600 text-white text-sm font-semibold hover:bg-cyan-700">Get started</button>
</div>
</div>
<script>
function openModal() {
document.body.classList.add("overflow-hidden");
document.getElementById("modal").classList.replace("opacity-0","opacity-100");
document.getElementById("modal").classList.remove("pointer-events-none");
document.getElementById("modal-panel").classList.replace("scale-95","scale-100");
}
function closeModal() {
document.body.classList.remove("overflow-hidden");
document.getElementById("modal").classList.replace("opacity-100","opacity-0");
document.getElementById("modal").classList.add("pointer-events-none");
document.getElementById("modal-panel").classList.replace("scale-100","scale-95");
}
</script>
Discussion