Modal
Dialog overlays for confirmations, forms, and content. Automatically converts to a bottom sheet on mobile. Supports native <dialog> and data-state patterns.
Basic Modal (data-state)
Preview
Modal Title
This is the modal content. Click outside or press the close button to dismiss.
<!-- Trigger -->
<button class="btn color-primary" onclick="document.getElementById('modal-1').dataset.state='open'">Open Modal</button>
<!-- Modal -->
<div id="modal-1" class="modal-backdrop" data-state="closed">
<div class="modal">
<div class="modal-header">
<h3 class="modal-title">Modal Title</h3>
<button class="modal-close" onclick="this.closest('.modal-backdrop').dataset.state='closed'">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12" /></svg>
</button>
</div>
<div class="modal-body">
<p>This is the modal content. Click outside or press the close button to dismiss.</p>
</div>
<div class="modal-footer">
<button class="btn btn-ghost" onclick="this.closest('.modal-backdrop').dataset.state='closed'">Cancel</button>
<button class="btn color-primary" onclick="this.closest('.modal-backdrop').dataset.state='closed'">Confirm</button>
</div>
</div>
</div> Requires: ux.min.css
<!-- Trigger -->
<button class="btn color-primary" onclick="document.getElementById('modal-tw').dataset.state='open'">Open Modal</button>
<!-- Modal backdrop (hidden by default) -->
<div id="modal-tw" class="fixed inset-0 flex items-center justify-center z-400 p-6 bg-black/40 opacity-0 invisible [&[data-state=open]]:opacity-100 [&[data-state=open]]:visible transition-all" data-state="closed">
<div class="relative flex flex-col w-full max-w-md max-h-[85dvh] bg-base-100 rounded-2xl shadow-xl overflow-hidden">
<div class="flex items-center justify-between p-5 border-b border-base-200">
<h3 class="text-lg font-semibold">Modal Title</h3>
<button class="size-8 rounded-full bg-base-200 inline-flex items-center justify-center opacity-60 hover:opacity-100" onclick="this.closest('[data-state]').dataset.state='closed'">
<svg class="size-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12" /></svg>
</button>
</div>
<div class="flex-1 overflow-y-auto p-5">
<p>This is the modal content.</p>
</div>
<div class="flex items-center justify-end gap-2 p-5 border-t border-base-200">
<button class="btn btn-ghost" onclick="this.closest('[data-state]').dataset.state='closed'">Cancel</button>
<button class="btn color-primary" onclick="this.closest('[data-state]').dataset.state='closed'">Confirm</button>
</div>
</div>
</div> Requires: tw.min.css
// Open modal
document.getElementById('modal-1').dataset.state = 'open';
// Close modal
document.getElementById('modal-1').dataset.state = 'closed';
// Close on backdrop click
document.querySelector('.modal-backdrop').addEventListener('click', (e) => {
if (e.target === e.currentTarget) {
e.target.dataset.state = 'closed';
}
});
// Close on Escape
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
document.querySelector('.modal-backdrop[data-state="open"]')?.setAttribute('data-state', 'closed');
}
}); Native <dialog>
Preview
<button class="btn color-primary" onclick="document.getElementById('dialog-1').showModal()">Open Dialog</button>
<dialog id="dialog-1" class="modal">
<div class="modal-header">
<h3 class="modal-title">Native Dialog</h3>
<button class="modal-close" onclick="this.closest('dialog').close()">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12" /></svg>
</button>
</div>
<div class="modal-body">
<p>Using the native HTML <dialog> element. Press Escape to close.</p>
</div>
<div class="modal-footer">
<button class="btn btn-ghost" onclick="this.closest('dialog').close()">Close</button>
</div>
</dialog> Requires: ux.min.css
<button class="btn color-primary" onclick="document.getElementById('dialog-tw').showModal()">Open Dialog</button>
<dialog id="dialog-tw" class="p-0 border-none max-w-md max-h-[85dvh] bg-base-100 rounded-2xl shadow-xl backdrop:bg-black/40">
<div class="flex items-center justify-between p-5 border-b border-base-200">
<h3 class="text-lg font-semibold">Native Dialog</h3>
<button class="size-8 rounded-full bg-base-200 inline-flex items-center justify-center opacity-60 hover:opacity-100" onclick="this.closest('dialog').close()">
<svg class="size-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12" /></svg>
</button>
</div>
<div class="p-5">
<p>Using native <dialog>. Press Escape to close.</p>
</div>
<div class="flex items-center justify-end gap-2 p-5 border-t border-base-200">
<button class="btn btn-ghost" onclick="this.closest('dialog').close()">Close</button>
</div>
</dialog> Requires: tw.min.css
// Native <dialog> API
const dialog = document.getElementById('dialog-1');
dialog.showModal(); // opens with backdrop
dialog.close(); // closes
// Escape key automatically closes native dialogs Zero-JS (Checkbox Hack)
Preview
Zero JavaScript!
This modal works with a hidden checkbox — no JS needed.
<!-- Hidden checkbox controls the modal -->
<input type="checkbox" id="modal-toggle-1" class="modal-toggle" />
<label for="modal-toggle-1" class="btn">Open (No JS)</label>
<div class="modal-backdrop">
<div class="modal modal-sm">
<div class="modal-body">
<h3 class="text-lg font-semibold mb-2">Zero JavaScript!</h3>
<p class="text-sm text-base-content/70">This modal works with a hidden checkbox — no JS needed.</p>
</div>
<div class="modal-footer">
<label for="modal-toggle-1" class="btn color-primary">Got it</label>
</div>
</div>
</div> Requires: ux.min.css
<!-- Hidden checkbox controls the modal -->
<input type="checkbox" id="tw-modal-toggle" class="hidden peer" />
<label for="tw-modal-toggle" class="btn cursor-pointer">Open (No JS)</label>
<div class="fixed inset-0 flex items-center justify-center z-400 p-6 bg-black/40 opacity-0 invisible peer-checked:opacity-100 peer-checked:visible transition-all">
<div class="relative flex flex-col w-full max-w-sm max-h-[85dvh] bg-base-100 rounded-2xl shadow-xl overflow-hidden">
<div class="flex-1 overflow-y-auto p-5">
<h3 class="text-lg font-semibold mb-2">Zero JavaScript!</h3>
<p class="text-sm text-base-content/70">This modal works with a hidden checkbox — no JS needed.</p>
</div>
<div class="flex items-center justify-end gap-2 p-5 border-t border-base-200">
<label for="tw-modal-toggle" class="btn color-primary cursor-pointer">Got it</label>
</div>
</div>
</div> Requires: tw.min.css
// No JavaScript required!
// Uses a hidden checkbox + CSS :checked ~ selector Confirm Dialog
Preview
Delete Item?
This action cannot be undone. The item and all associated data will be permanently deleted.
<!-- Trigger -->
<button class="btn color-error" onclick="document.getElementById('confirm-1').dataset.state='open'">Delete Item</button>
<!-- Confirm Dialog — modal-sm with centered icon layout -->
<div id="confirm-1" class="modal-backdrop" data-state="closed">
<div class="modal modal-sm">
<div class="modal-body text-center py-8">
<div class="inline-flex items-center justify-center size-14 rounded-full bg-error/10 text-error mb-4">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5" class="size-7"><path stroke-linecap="round" stroke-linejoin="round" d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0" /></svg>
</div>
<h3 class="text-lg font-semibold mb-2">Delete Item?</h3>
<p class="text-sm text-base-content/60">This action cannot be undone. The item and all associated data will be permanently deleted.</p>
</div>
<div class="modal-footer">
<button class="btn btn-ghost flex-1" onclick="this.closest('.modal-backdrop').dataset.state='closed'">Cancel</button>
<button class="btn color-error flex-1" onclick="this.closest('.modal-backdrop').dataset.state='closed'">Delete</button>
</div>
</div>
</div> Requires: ux.min.css
<!-- Trigger -->
<button class="btn color-error" onclick="document.getElementById('confirm-tw').dataset.state='open'">Delete Item</button>
<!-- Confirm Dialog -->
<div id="confirm-tw" class="fixed inset-0 flex items-center justify-center z-400 p-6 bg-black/40 opacity-0 invisible [&[data-state=open]]:opacity-100 [&[data-state=open]]:visible transition-all" data-state="closed">
<div class="relative flex flex-col w-full max-w-sm bg-base-100 rounded-2xl shadow-xl overflow-hidden">
<div class="text-center px-6 py-8">
<div class="inline-flex items-center justify-center size-14 rounded-full bg-error/10 text-error mb-4">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5" class="size-7"><path stroke-linecap="round" stroke-linejoin="round" d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0" /></svg>
</div>
<h3 class="text-lg font-semibold mb-2">Delete Item?</h3>
<p class="text-sm text-base-content/60">This action cannot be undone. The item and all associated data will be permanently deleted.</p>
</div>
<div class="flex items-center gap-2 p-5 border-t border-base-200">
<button class="btn btn-ghost flex-1" onclick="this.closest('[data-state]').dataset.state='closed'">Cancel</button>
<button class="btn color-error flex-1" onclick="this.closest('[data-state]').dataset.state='closed'">Delete</button>
</div>
</div>
</div> Requires: tw.min.css
// Open confirm dialog
document.getElementById('confirm-1').dataset.state = 'open';
// Close on confirm action
function confirmDelete() {
// Perform action...
document.getElementById('confirm-1').dataset.state = 'closed';
}
// Alpine.js pattern (recommended):
// <div x-data="{ confirm: false }">
// <button @click="confirm = true">Delete</button>
// <div class="modal-backdrop" :data-state="confirm ? 'open' : 'closed'">
// <div class="modal modal-sm">
// <div class="modal-body text-center py-8">
// <div class="inline-flex items-center justify-center size-14 rounded-full bg-error/10 text-error mb-4">...</div>
// <h3 class="text-lg font-semibold mb-2">Delete Item?</h3>
// <p class="text-sm text-base-content/60">Description text...</p>
// </div>
// <div class="modal-footer">
// <button class="btn btn-ghost flex-1" @click="confirm = false">Cancel</button>
// <button class="btn color-error flex-1" @click="confirm = false; doAction()">Delete</button>
// </div>
// </div>
// </div>
// </div> Sizes
...
...
...
...
...
<!-- Small: max-width 400px -->
<div class="modal modal-sm">...</div>
<!-- Default: max-width 500px -->
<div class="modal">...</div>
<!-- Large: max-width 800px -->
<div class="modal modal-lg">...</div>
<!-- Extra large: max-width 1140px -->
<div class="modal modal-xl">...</div>
<!-- Fullscreen -->
<div class="modal modal-fullscreen">...</div> Requires: ux.min.css
<!-- Small: max-width 400px -->
<div class="relative flex flex-col w-full max-w-sm bg-base-100 rounded-2xl shadow-xl overflow-hidden p-5">Small (max-w-sm)</div>
<!-- Default: max-width 500px -->
<div class="relative flex flex-col w-full max-w-md bg-base-100 rounded-2xl shadow-xl overflow-hidden p-5">Default (max-w-md)</div>
<!-- Large: max-width 800px -->
<div class="relative flex flex-col w-full max-w-3xl bg-base-100 rounded-2xl shadow-xl overflow-hidden p-5">Large (max-w-3xl)</div>
<!-- Extra large: max-width 1140px -->
<div class="relative flex flex-col w-full max-w-6xl bg-base-100 rounded-2xl shadow-xl overflow-hidden p-5">XL (max-w-6xl)</div>
<!-- Fullscreen -->
<div class="relative flex flex-col w-full h-full max-w-none bg-base-100 rounded-none shadow-xl overflow-hidden p-5">Fullscreen</div> Requires: tw.min.css
// No JavaScript required for sizing Classes Reference
| Class | Description |
|---|---|
| .modal-backdrop | Fixed overlay container (use data-state="open/closed") |
| .modal | Modal dialog box |
| .modal-header | Header with title and close |
| .modal-title | Modal heading |
| .modal-close | Close button (circle) |
| .modal-body | Scrollable content area |
| .modal-footer | Action buttons area |
| .modal-handle | Mobile drag handle (auto-shown) |
| .modal-sm | Small (400px) |
| .modal-lg | Large (800px) |
| .modal-xl | Extra large (1140px) |
| .modal-fullscreen | Full screen modal |
| .modal-toggle | Hidden checkbox for zero-JS modal |
| dialog.modal | Native <dialog> styling |