Create a modal with the HTML dialog element, Tailwind and Stimulus
Tags: tailwindcss, html, stimulus, javascript
Published: 2022-07-29 08:24:00

Support for the dialog element is now widespread, with safari being the last of the main browsers to provide [native support](https://caniuse.com/?search=dialog).

The [dialog HTML element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog) 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](https://stimulus.hotwired.dev/). 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](https://gist.github.com/thomasvanholder/4e71ffea4bcdb4984b466c7758dbfa36) for a full gist of the CSS, HTML and JS.

---

- [1. Add button and dialog](#1-add-button-and-dialog)
- [2. Close modal by esc key](#2-close-modal-by-esc-key)
- [3. Close modal by backdrop click](#3-close-modal-by-backdrop-click)
- [4. Reset form after modal close](#4-reset-form-after-modal-close)
- [5. Change default backdrop style](#5-change-default-backdrop-style)
- [6. Add animation](#6-add-animation)

---

## 1. Add button and dialog

To start, let's create a `button` and `dialog` element wrapped in a [stimulus controller](https://stimulus.hotwired.dev/reference/controllers).

```erb
<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.
```js
//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()](https://developer.mozilla.org/en-US/docs/Web/API/HTMLDialogElement/showModal) method displays the modal  on the top layer and adds a default background. Any interaction outside the dialog is inaccessible.
- The [close()](https://developer.mozilla.org/en-US/docs/Web/API/HTMLDialogElement/close) method simply closes the dialog.

![Button and dialog](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/smwt9vilijlq2iqft142.gif)

--- 

## 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` and `form` element instead.

```html
<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.

```js
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

![reset form](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mv6pcqj7ad15x79c0mjd.gif)

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.

```html
<form class="p-4 space-y-4" data-modal-target="form">
  ...
</form>
```

```js
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.

```html
<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](https://tailwindcss.com/docs/animation#customizing-your-theme) 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.

```js
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](https://tailwindcss.com/docs/hover-focus-and-other-states#open-closed-state): `open:animate-fade-in`.

```html
<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`.

```html
<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.

![Final](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w136ut9gudn642k7bw7o.gif)