How to create a native dialog
Learn how to create a native dialog using TailwindCSS v4.
Native dialogs (<dialog>
) are a lightweight way to display modals without relying on external JavaScript libraries. They come with built-in browser support for methods like showModal()
and close()
. TailwindCSS makes styling these dialogs a breeze.
Step 1: Write and Style the Dialog
Start by creating a dialog and a button to trigger it. We’ll use Tailus UI HTML classes to style the dialog and button.
<div>
<button
data-dialog-trigger="dialog1"
class="btn sz-md variant-outlined"
>
<span class="btn-label">Open Dialog</span>
</button>
<dialog
role="dialog"
id="dialog1"
aria-labelledby="dialog1_label"
class="shadow-xl size-fit p-8 m-auto max-w-lg card variant-outlined origin-bottom backdrop:bg-gray-950/50"
>
<h2 id="dialog1_label" class="text-title font-medium">Add Delivery Address</h2>
<p class="text-body text-sm mt-2">The refund will be reflected in the customer’s bank account 2 to 3 business days after processing.</p>
<form action="post" id="userAddress" class="mt-6">
<div data-shade="950" class="field">
<label class="text-title font-medium text-sm" for="address">Address</label>
<input type='text' id="address" placeholder="Lubumbashi" required class="input variant-mixed sz-md rounded-btn"/>
</div>
</form>
<div class="mt-8 flex justify-end gap-3">
<button data-dialog-close class="btn sz-sm variant-outlined">
<span class="btn-label">Cancel</span>
</button>
<button class="btn sz-sm variant-primary" type="submit" form="userAddress">
<span class="btn-label">Save</span>
</button>
</div>
</dialog>
</div>
Attribute Breakdown:
-
data-dialog-trigger
: Links the button to the dialog by matching this attribute’s value (dialog1) with the id of the dialog. Example:<button data-dialog-trigger="dialog1">Open Dialog</button>
-
data-dialog-close
: Adds a close action to buttons inside the dialog. Example:<button data-dialog-close>Close</button>
Step 2 : Add transition
Enhance the dialog with smooth animations for opening and closing. We’ll use the open
and starting
variants with TailwindCSS’s @starting-style
support.
Add the following classes to the <dialog>
element:
<dialog
role="dialog"
id="dialog1"
aria-labelledby="dialog1_label"
class="shadow-xl size-fit p-8 m-auto max-w-lg card variant-outlined origin-bottom backdrop:bg-gray-950/50
transition-all duration-300 transition-discrete opacity-0 scale-95 open:opacity-100 open:scale-100 starting:open:opacity-0 starting:open:scale-95"
>
Explanation of Classes:
opacity-0
andscale-95
: Initial state when the dialog is not visible.open:opacity-100
andopen:scale-100
: Final state when the dialog is open.starting:open:opacity-0
andstarting:open:scale-95
: Ensures transitions begin from a visually smooth starting point.
Why @starting-style is Used for Dialog Transitions
In CSS, it’s not possible to transition the display property (e.g., from none
to block
), as it’s not animatable. Native dialogs (<dialog>
) rely on the open attribute to control their visibility, and this can cause an abrupt appearance/disappearance when using showModal()
or close()
.
The @starting-style
feature solves this problem by allowing you to define a temporary “starting state” for animations. This ensures smooth transitions when the open attribute is toggled.
Step 3: Add the JavaScript
The followind code enables dialog functionality for multiple dialogs using the attributes defined above.
const dialogTriggers = document.querySelectorAll('[data-dialog-trigger]');
dialogTriggers.forEach((dialogTrigger) => {
const dialogId = dialogTrigger.getAttribute('data-dialog-trigger');
if (!dialogId) {
console.error('Dialog trigger is missing a valid "data-dialog-trigger" attribute.');
return;
}
const dialog = document.getElementById(dialogId);
if (!dialog) {
console.error(`Dialog with ID "${dialogId}" not found.`);
return;
}
const dialogClose = dialog.querySelector('[data-dialog-close]');
if (!dialogClose) {
console.error(`Close button not found in dialog with ID "${dialogId}".`);
return;
}
dialogTrigger.addEventListener('click', () => {
document.body.classList.add('overflow-hidden');
dialog.showModal()
});
dialogClose.addEventListener('click', () => {
document.body.classList.remove('overflow-hidden');
dialog.close()
});
});
This code:
- Finds all buttons with data-dialog-trigger.
- Matches the button’s trigger ID to a dialog id.
- Handles opening and closing dialogs using
showModal()
andclose()
. - Prevents scrolling when a dialog is open by adding/removing overflow-hidden to/from the
<body>
.
This setup is scalable and reusable, so you can easily add more dialogs without extra scripts.
Common Mistakes to Avoid
- Not linking the button and dialog correctly with
data-dialog-trigger
. - Forgetting to add
data-dialog-close
to the close button. - Not handling the
overflow-hidden
class on the<body>
element to prevent background scrolling.
Conclusion
Native dialogs are a great way to create modals without relying on external libraries. TailwindCSS makes it easy to style these dialogs with its utility classes. By following the steps above, you can create a native dialog that is both functional and visually appealing.