Making dialog with HTML Dialog element
Tags: html, a11y, tutorials, webdev
Published: 2023-06-05 20:17:34

---
title: "Making dialog with HTML Dialog element"
published: true
description: "How to build an accessible dialog component using the native HTML dialog element"
tags: ["html", "a11y", "tutorials", "webdev"]
cover_image: "https://res.cloudinary.com/mayashavin/image/upload/f_auto,q_auto/mayashavin/articles/html_dialog_element"
canonical_url: "https://mayashavin.com/articles/build-a-dialog-with-dialog-element"
---

_Dialogs and modals are the essential components for any web application. Still, it takes quite an effort for us to create and define default behavior logic for it using the existing `div` element. But with `dialog` element, things can be easier. Let's check it out in this post, shall we?_

## Introducing `dialog` element

Dialog element `dialog` is a pretty new HTML5 element, and you can use it with the `<dialog>` markup, as shown in the example below: 

```html
<dialog>
    <div>
        I'm a dialog!
    </div>
</dialog>
```

Unlike other HTML5 elements, the `dialog` element comes with built-in APIs and some basic CSS stylings. By design, the `dialog` element has two states - _visible_ (open) or _hidden_ (close), indicated by a single property `open`:

```html
<dialog open>
    <div>
        This dialog is opened and visible!
    </div>
</dialog>

<dialog>
    <div>
        This dialog is hidden!
    </div>
</dialog>
```

The initial state of `dialog` is "_hidden_". The `dialog` element exposes the following API methods to control its conditions:

* `showModal()` - open the dialog by setting the attribute `open` to the element. It also adds a `::backdrop` pseudo-element to cover the content outside of the element as an overlay effect.
* `show()` - similar like `showModal`, but without adding a backdrop. This method is useful for using the `dialog` as a toast notification.
* `close(newReturnValue)` - close the dialog by removing the `open` attribute, and updating the `returnValue` of the `dialog` element if there is any `newReturnValue` passed.

With the given APIs above, let's implement a simple dialog next.

## Create a simple dialog

We are going to create a simple dialog containing three sections:

* `header`, which has the dialog title and a cancel button (with an X indicator)
* `div` section where we display all the content for the dialog. In our example, it will be just a simple string.
* `footer` contains a `menu` of action buttons, such as Cancel and Confirm buttons.

A sample sketch of our dialog is as below:

<img src="https://res.cloudinary.com/mayashavin/image/upload/q_auto,f_auto/v1652993964/mayashavin/articles/Dialog_design" loading="lazy" alt="Sketch design of dialog element divided into three sections of header, content and footer actions" class="mx-auto"/>

Now let's add a `dialog` markup element with the above sections to the `body` section of an `HTML` file. The `dialog` has an `id` attribute with the value `main-dialog`, as shown below:

```html
<html>
    <body>
        <dialog id="main-dialog">
            <header>
                <h2>Dialog header</h2>
                <button id="cancel-btn">X</button>
            </header>
            <div>
                Dialog content
            </div>
            <footer>
                <menu>
                    <button id="cancel-btn">Cancel</button>
                    <button id="confirm-btn">Confirm</button>
                </menu>
            </footer>
        </dialog>
    </body>
</html>
```

Next, we add to the `body` a `main` element that contains a `button` to open the dialog:

```html
<body>
    <main id="app">
        <button id="open-dialog-btn">Open dialog</button>
    </main>
    <dialog id="main-dialog">
        <!--....-->
    </dialog>
</body>
```

When running the above code on the browser, you won't see the dialog as it is not visible by default. We need to bind the `onclick` event of the `button` with `id` "_open-dialog-btn_" using the `addEventListener` method as follows:

```javascript

//1. Query for open dialog button
const openBtn = document.getElementById('open-dialog-btn');

//2. Query for dialog element
const dialog = document.getElementById('main-dialog');

//3. Add event listener to open dialog on click
openBtn.addEventListener('click', () => {
    dialog.showModal();
})
```

Refresh the browser, and you now can see the dialog is open whenever you click the _Open dialog_ button:

<img src="https://res.cloudinary.com/mayashavin/video/upload/q_auto/v1652989391/mayashavin/articles/open_dialog_demo.gif" loading="lazy" alt="Demo on how to open dialog using a button" class="mx-auto"/>

Once the dialog is open, we should bind the other menu buttons and the header button to the proper event handlers and close the dialog with `dialog.close()` accordingly. 

```javascript
//1. Query for the buttons
const cancelBtn = document.getElementById('cancel-btn');
const confirmBtn = document.getElementById('confirm-btn');
const headerCancelBtn = document.getElementById('cancel-btn');

//2. Bind to onclick event
cancelBtn.addEventListener('click', () => dialog.close());
confirmBtn.addEventListener('click', () => dialog.close());
headerCancelBtn.addEventListener('click', () => dialog.close())
```

We now have the component fully workable. 

<img src="https://res.cloudinary.com/mayashavin/video/upload/q_auto/v1652989391/mayashavin/articles/full_flow_dialog.gif" loading="lazy" alt="Full flow demo on opening dialog and closing dialog by clicking on dialog buttons" class="mx-auto"/>

What about accessibility? Does `dialog` offer any accessibility support? Let's find out next.

## Accessibility with `dialog`

An accessible dialog needs the following characteristics:

* When the dialog is open using keyboard navigation, the browser should auto-focus on the first button within the dialog. In our case, it is the close button at the dialog header.
* After closing the dialog, the browser should restore the focus on the button we used to open the dialog, allowing users to resume their navigation flow onwards.
* The screen reader should be able to announce the dialog's label, and optionally, its description when entering the dialog.

To set the focus on the first visible button in the dialog, we use the dialog's `autofocus` attribute. For its label and description, we can assign the WAI ARIA attributes `aria-labelledby` and `aria-describedby` to the target elements' id selectors, respectively. We also add `role="dialog"` (or `role="alert"`) for the screen readers to announce the dialog properly, as seen in the code below:

```html
<dialog 
   id="main-dialog" 
   autofocus
   role="dialog"
   aria-describedby="content-area"
   aria-labelledby="label-area"
>
  <header>
    <h2 id="label-area">Dialog header</h2>
    <button id="dialog-header--cancel-btn">X</button>
  </header>
  <div id="content-area">
    Dialog content
  </div>
  <!-- ... -->
</dialog>
``` 
One nice thing about using `dialog` element is that when user closes the dialog, the browser automatically restores the focus on the button we used to open it. And that applies with ESC key events, too!

Below is a demonstration of how the navigation keyboard and focus look like with dialog element:

<img src="https://res.cloudinary.com/mayashavin/video/upload/q_auto/v1652989391/mayashavin/articles/tab_nav_dialog.gif" loading="lazy" alt="Demo on how dialog works when using Tab key navigation" class="mx-auto"/>

**Note**

If you want to add an animation effect on closing and opening dialog programmatically, note that you will **lose** this built-in feature support and have to implement the tab navigation focus yourself.

So far, so good? Next, we will look at how we use the form inside dialog.

## Using forms in dialogs

The `dialog` element provides built-in support for `form`. To enable the connection between dialog and its nested form, we set the `method` attribute of the `form` element to `dialog`. Then we wrap the original content within the `form` element, as seen below:

```html
<dialog
   id="main-dialog" 
   autofocus
   <!--...-->
>
    <form method='dialog'>
      <!--The rest of the code -->
    </form>
</dialog>
```

By doing so, triggering any form's button elements (except the button with `type='reset"`) will also close the dialog automatically, instead of manually binding the `close()` function on each of them.

We can now remove all the onclick event listeners for closing the dialog and it should function as same as previously.

Cool, right? What if you want to trigger additional logic whenever the dialog is closed from outside of the dialog? For that, we use a `close` event listener.

## Listening to the `close` event of a dialog

`dialog` has a `close` event that we can attach an event handler to using the following syntax:

```javascript
dialog.addEventListener('close', () => {
    //your handler logic
})
```

Whenever a user closes the dialog, it will trigger this handler. We then can use `value` attribute in the buttons and access them using `dialog.returnValue` to decide whether the user clicks on a Cancel button or a Submit button, as follows:

```html
<menu>
    <button id="dialog-footer--cancel-btn" value="cancel">Cancel</button>
    <button id="dialog-footer--cancel-btn" value="confirm">Confirm</button>
</menu>
```

Within the `close` event listener logic, we can perform a simple conditional logic:

```javascript
dialog.addEventListener('close', () => {
    if (dialog.returnValue === 'cancel') {
        //do something
    } else if (dialog.returnValue === 'confirm) {
        //do something
    }
})
```

**Note** here that if the user hits the ESC key, there won't be any value for `returnValue`.

That's it! We have created our first dialog using the `dialog` element.

## Demo

The demo code is available for trying. 👇

[![Experimenting HTML dialog element](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/embed/dialog-element-demo-forked-5qyc7e?fontsize=14&hidenavigation=1&theme=dark)

## Browser support

There is full support for `dialog` and its APIs in [all major browsers except Internet Explorer](https://developer.mozilla.org/en-US/docs/Web/API/HTMLDialogElement). We now can enjoy the benefit of a built-in dialog element and start customizing it to our needs. Awesome, isn't it?

---

## Summary

We have gone through the basics of building a dialog component using HTML and DOM APIs in this post. With the `dialog` element, composing a reusable Dialog component in the front-end framework becomes more straightforward. You can also use the same approach to create a Modal or Toast notification component. 

Next, how about trying to make a Dialog component in your favorite framework like Vue or React using the `dialog` element? It should be fun 😉!

👉 _If you'd like to catch up with me sometimes, follow me on [Twitter](https://twitter.com/MayaShavin) | [Facebook](https://www.facebook.com/mayashavin/)._

👉 _Learn about Vue with my new book [Learning Vue](https://www.oreilly.com/library/view/learning-vue/9781492098812/). The early release is available now!_

Like this post or find it helpful? Share it 👇🏼 😉