DEV Community

Cover image for Making dialog with HTML Dialog element
Maya Shavin πŸŒ·β˜•οΈπŸ‘
Maya Shavin πŸŒ·β˜•οΈπŸ‘

Posted on • Updated on • Originally published at mayashavin.com

Making dialog with HTML 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:

<dialog>
    <div>
        I'm a dialog!
    </div>
</dialog>
Enter fullscreen mode Exit fullscreen mode

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:

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

<dialog>
    <div>
        This dialog is hidden!
    </div>
</dialog>
Enter fullscreen mode Exit fullscreen mode

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:

Sketch design of dialog element divided into three sections of header, content and footer actions

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>
    <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>
Enter fullscreen mode Exit fullscreen mode

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

<body>
    <main id="app">
        <button id="open-dialog-btn">Open dialog</button>
    </main>
    <dialog id="main-dialog">
        <!--....-->
    </dialog>
</body>
Enter fullscreen mode Exit fullscreen mode

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:


//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();
})
Enter fullscreen mode Exit fullscreen mode

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

Demo on how to open dialog using a button

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.

//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())
Enter fullscreen mode Exit fullscreen mode

We now have the component fully workable.

Full flow demo on opening dialog and closing dialog by clicking on dialog buttons

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:

<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>
Enter fullscreen mode Exit fullscreen mode

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:

Demo on how dialog works when using Tab key navigation

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:

<dialog
   id="main-dialog" 
   autofocus
   <!--...-->
>
    <form method='dialog'>
      <!--The rest of the code -->
    </form>
</dialog>
Enter fullscreen mode Exit fullscreen mode

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:

dialog.addEventListener('close', () => {
    //your handler logic
})
Enter fullscreen mode Exit fullscreen mode

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:

<menu>
    <button id="dialog-footer--cancel-btn" value="cancel">Cancel</button>
    <button id="dialog-footer--cancel-btn" value="confirm">Confirm</button>
</menu>
Enter fullscreen mode Exit fullscreen mode

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

dialog.addEventListener('close', () => {
    if (dialog.returnValue === 'cancel') {
        //do something
    } else if (dialog.returnValue === 'confirm) {
        //do something
    }
})
Enter fullscreen mode Exit fullscreen mode

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

Browser support

There is full support for dialog and its APIs in all major browsers except Internet Explorer. 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 | Facebook.

πŸ‘‰ Learn about Vue with my new book Learning Vue. The early release is available now!

Like this post or find it helpful? Share it πŸ‘‡πŸΌ πŸ˜‰

Top comments (7)

Collapse
 
itsraghz profile image
Raghavan alias Saravanan Muthu

Good one, thanks for sharing!

Collapse
 
fruntend profile image
fruntend

Π‘ongratulations πŸ₯³! Your article hit the top posts for the week - dev.to/fruntend/top-10-posts-for-f...
Keep it up πŸ‘

Collapse
 
mayashavin profile image
Maya Shavin πŸŒ·β˜•οΈπŸ‘

thank you! ;)

Collapse
 
steady5063 profile image
Mark Steadman

Love the inclusion of accessibility in this article!

Collapse
 
mayashavin profile image
Maya Shavin πŸŒ·β˜•οΈπŸ‘

Thank you πŸ₯°

Collapse
 
isaacojeda profile image
Isaac Ojeda

Nice postπŸ‘πŸ»

Collapse
 
mayashavin profile image
Maya Shavin πŸŒ·β˜•οΈπŸ‘

Thank you 😊