Support for the dialog element is now widespread, with safari being the last of the main browsers to provide native support.
The dialog HTML element is an appealing choice for creating a modal as it comes with built-in functionality for opening and closing modals.
This tutorial uses Stimulus as a light-weight JavaScript framework. To learn more about Stimulus, visit the docs. When you use vanilla JS, you can mimic the data-action
and data-target
behavior in by utilizing the appropriate document.querySelector
s and eventListener
s instead.
The form and button styling are extrapolated to improve readability. See here for a full gist of the CSS, HTML and JS.
- 1. Add button and dialog
- 2. Close modal by esc key
- 3. Close modal by backdrop click
- 4. Reset form after modal close
- 5. Change default backdrop style
- 6. Add animation
1. Add button and dialog
To start, let's create a button
and dialog
element wrapped in a stimulus controller.
<div data-controller="modal">
<button data-action="modal#open" class="btn-outline">Open modal</button>
<dialog data-modal-target="modal">
<button type="button" data-action="modal#close" class="absolute top-4 right-4">X</button>
<h1>Reset your password</h1>
<hr class="mb-4">
<form class="space-y-4">
<label>Email</label>
<input type="text">
<button type="button" data-action="modal#close" class="btn-outline">Close</button>
<button type="button" class="btn-blue">Reset Password</button>
</form>
</dialog>
</div>
The corresponding stimulus controller is pretty barebones.
//app/javascript/controllers/modal_controller.js
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
static targets = ["modal"]
open(event) {
event.preventDefault();
this.modalTarget.showModal();
}
close(event) {
event.preventDefault();
this.modalTarget.close();
}
}
- The showModal() method displays the modal on the top layer and adds a default background. Any interaction outside the dialog is inaccessible.
- The close() method simply closes the dialog.
2. Close modal by esc key
This is an easy one. Closing the modal by escape key is included by default when utilising the dialog element. Nice!
3. Close modal by backdrop click
- Remove all default padding on the dialog element to ensure only a background click triggers the closing of the modal.
- Add padding on the
h1
andform
element instead.
<dialog data-modal-target="modal" class="p-0">
...
<h1 class="p-4">Reset your password</h1>
<form class="p-4 space-y-4">
...
</form>
</dialog>
- Add an
eventListener
that initiates after opening the modal to listen for the backdrop click.
export default class extends Controller {
// ....
open(event) {
event.preventDefault();
this.modalTarget.showModal();
this.modalTarget.addEventListener('click', (e) => this.backdropClick(e));
}
backdropClick(event) {
event.target === this.modalTarget && this.close(event)
}
}
- The
backdropClick()
method verifies if the clicked element is the dialog and, if so, closes the modal.
4. Reset form after modal close
The form still populates previously filled input after closing and re-opening the modal. To start the modal with empty inputs, add a form target and reset the form.
<form class="p-4 space-y-4" data-modal-target="form">
...
</form>
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
static targets = ["modal", "form"]
close(event) {
event.preventDefault();
this.modalTarget.close();
this.formTarget.reset() // or find by id selector
}
}
5. Change default backdrop style
Add the backdrop
selector to the dialog html element.
<dialog class="p-0 backdrop:bg-gray-400 backdrop:bg-opacity-50">
...
</dialog>
6. Add animation
The default animation is quite abrupt and appears instantaneous on the screen. To make the transition smoother, let's add a fade-in custom animation with a duration of 0.5 seconds.
In the tailwind.config.js
file, add a fade-in keyframe
and animation
. The effect can be applied using the animate-fade-in
class.
module.exports = {
// ...
theme: {
extend: {
keyframes: {
"fade-in": {
'0%': { opacity: '0%' },
'100%': { opacity: '100%' },
}
},
animation: {
"fade-in": 'fade-in 0.5s ease-in-out',
}
}
}
}
Next, add the newly created animation to the dialog element using the open modifier: open:animate-fade-in
.
<dialog data-modal-target="modal" class="p-0 backdrop:bg-gray-400 backdrop:bg-opacity-50 open:animate-fade-in">
...
</dialog>
You can easily add this animation to the backdrop as well: open:backdrop:animate-fade-in
.
<dialog data-modal-target="modal" class="p-0 backdrop:bg-gray-400 backdrop:bg-opacity-50 open:animate-fade-in open:backdrop:animate-fade-in">
...
</dialog>
That's it! Using the dialog element simplifies creating modals by leveraging its build-in API features.
Top comments (4)
instead of
you can use
as you mentioned on the beginning of the article, isn't the whole point of stimulus to not use eventlistener?
just add
<dialog data-action="click->modal#clickOutside">
andHow do you prevent the content behind the dialog from scrolling when opening and closing the dialog. I have to scroll down on my page to get to the button that opens the dialog, but when I open the dialog the page jumps back to the top.
something like this: