Modals with HTML dialog element, TailwindCSS and StimulusJS
Safari has finally adopted the <dialog>
HTML element, and now it is supported by all browsers!
HTML <dialog>
is basically a “modal”:
- centered on page by default
- disables background clicks when open
- can be closed with native HTML (without extra JS)
- includes CSS to blur/dim background by default
- can be closed with Escape key
Example:
Display basic dialog: #
<dialog open>
<span>You can see me</span>
</dialog>
Dialog with “Close” button (without submitting form): #
method="dialog"
on form
<dialog open>
<span>You can see me</span>
<form method="dialog">
<button type="submit" autofocus>Cancel</button>
</form>
</dialog>
Dialog with both “Close” button and regular “Submit” button on form: #
formmethod="dialog"
on button
<dialog open>
<span>You can see me</span>
<form>
abc
<button formmethod="dialog" type="submit">Cancel</button>
<button>Submit</button>
</form>
</dialog>
With button to open modal: #
<div data-controller="dialog">
<button data-action="dialog#open">
Open modal
</button>
<dialog data-dialog-target="modal">
<span>You can see me</span>
<form method="dialog">
<button type="submit" autofocus>Cancel</button>
</form>
</dialog>
</div>
// app/javascript/controllers/dialog_controller.js
static targets = ["modal"]
open() {
this.modalTarget.showModal()
// this.modalTarget.show()
}
-
.show()
- background is clickable, can be used like a regular dropdown -
.showModal()
- background is not clickable, you can apply css styles likeblur
. You can use “Esc” key close it!
Most likely you want to use exclusively .showModal()
.
Background blur, color, opacity #
dialog::backdrop {
backdrop-filter: blur(8px);
background-color: hsl(250, 100%, 50%, 0.25);
}
Close on click outside #
clickOutside(event) {
if (event.target === this.dialogTarget) {
this.close()
}
}
-<div data-controller="dialog">
+<div data-controller="dialog" data-action="click->dialog#clickOutside">
Disable background scrolling when dialog is open #
open() {
this.dialogTarget.showModal()
document.body.classList.add("overflow-hidden");
}
close() {
this.dialogTarget.close()
document.body.classList.remove("overflow-hidden");
}
Important: It will not work with the default behaviour of closing by clicking Escape
or by method="dialog"
.
To make it actually work you need to listen to the close
event on <dialog>
:
connect() {
this.modalTarget.addEventListener("close", this.enableBodyScroll.bind(this))
}
enableBodyScroll() {
document.body.classList.remove('overflow-hidden')
}
Blur background #
/* app/assets/stylesheets/application.css */
dialog::backdrop {
backdrop-filter: blur(8px);
/* background-color: hsl(250, 100%, 50%, 0.25); */
}
Final result #
- ✅ Styled modal
- ✅ Blur background
- ✅ Close on Escape
- ✅ Close on click outside
- ✅ Close on clicking button
<div data-controller="dialog" data-action="click->dialog#clickOutside">
<button data-action="click->dialog#open">Open dialog</button>
<dialog data-dialog-target="modal"
class="backdrop:bg-gray-400 backdrop:bg-opacity-90 z-10 rounded-md border-4 bg-sky-900 w-full md:w-2/3 mt-24">
<div class="p-8">
<button class="bg-slate-400" data-action="dialog#close">Cancel</button>
<p>Greetings, one and all!</p>
<form>
<button formmethod="dialog">Cancel</button>
<button>OK</button>
</form>
</div>
</dialog>
</div>
// app/javascript/controllers/dialog_controller.js
import { Controller } from "@hotwired/stimulus"
// Connects to data-controller="dialog"
export default class extends Controller {
static targets = ["modal"]
connect() {
this.modalTarget.addEventListener("close", this.enableBodyScroll.bind(this))
}
disconnect() {
this.modalTarget.removeEventListener("close", this.enableBodyScroll.bind(this))
}
open() {
// this.modalTarget.show()
this.modalTarget.showModal()
document.body.classList.add('overflow-hidden')
}
close() {
this.modalTarget.close()
// document.body.classList.remove('overflow-hidden')
}
enableBodyScroll() {
document.body.classList.remove('overflow-hidden')
}
clickOutside(event) {
if (event.target === this.modalTarget) {
this.close()
}
}
}
Inspired by:
- https://blog.webdevsimplified.com/2023-04/html-dialog/
- https://dev.to/thomasvanholder/create-a-modal-with-the-html-dialog-element-tailwind-and-stimulus-573b
To explore in the future:
- submitting a form with errors
- submitting a form with
format.html
,format.turbo_stream
That’s it for now! 🤠
Did you like this article? Did it save you some time?