Dialogs with Style: The New HTML dialog Element

DIALOGS WITH STYLE: THE NEW HTML <dialog> ELEMENT

By Mark Volkmann, OCI Partner and Principal Software Engineer

May 2019

The <dialog> element was added to HTML in version 5.2. It provides a way to define dialogs that are initially closed. These can be styled with CSS.

The DOM interface that implements this functionality is HTMLDialogElement. It supports three methods:

  • The showModal method opens the dialog as a modal, which means the user cannot interact with elements outside it.
  • The show method opens the dialog as a non-modal, which means the user can interact with elements outside it.
  • The close method closes the dialog.

Past Alternatives

Before this addition, there were two primary options for implementing dialogs in web applications.

First, the JavaScript functions alertconfirm, and prompt can be used to display simple dialogs. These are not desirable because they cannot be made to match the styles of web applications that use them.

Second, dialogs can be simulated with <div> tags that have a higher CSS z-index than the page. There are many libraries for many web frameworks that take this approach. One example is react-modal.

These approaches are no longer needed now that we can use the <dialog> element.

Browser Support

As of January 25, 2019, the <dialog> element was supported by Chrome, Firefox, and Opera, but not by Edge, Internet Explorer, or Safari. However, there is a polyfill named "dialog-polyfill" from the Google Chrome team that allows it to work in all of these browsers. It is available at https://github.com/GoogleChrome/dialog-polyfill.

Polyfill Install

If Node.js is not installed, browse https://nodejs.org, download the "LTS" or "Current" version, and run the installer.

Install the polyfill by entering npm install dialog-polyfill.

Copy the files dialog-polyfill.css and dialog-polyfill.js from the node_modules/dialog-polyfill directory into the directory where index.html resides in the web application that wishes to use the <dialog> element. Typically this is the public directory.

Example Usage in Plain JavaScript

Here is an example of using the <dialog> element without using a web app framework.

Before the dialog is opened, the page has the following content:

html dialog closed

After the dialog is opened, the page has the following content:

html dialog open

index.html

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <link rel="stylesheet" href="dialog-polyfill.css" />
  5. <link rel="stylesheet" href="dialog-demo.css" />
  6. <script src="dialog-polyfill.js"></script>
  7. <script src="dialog-demo.js"></script>
  8. <title>Dialog Demo</title>
  9. </head>
  10. <body>
  11. <!-- This defines the content of the dialog which
  12. can be any HTML, including form elements. -->
  13. <dialog>
  14. I'm a dialog!
  15. <!-- This renders a "Close" button that will
  16. close the dialog when it is clicked. -->
  17. <form method="dialog"><input type="submit" value="Close" /></form>
  18. </dialog>
  19.  
  20. <div>This is the page content.</div>
  21.  
  22. <!-- Pressing this button causes the dialog to open. -->
  23. <button onClick="dialog.showModal()">Open...</button>
  24. </body>
  25. </html>

dialog-demo.js

  1. // Tell ESLint that about global variables.
  2. /* global dialogPolyfill: false */
  3.  
  4. // eslint-disable-next-line no-unused-vars
  5. let dialog; // accessed in index.html
  6.  
  7. window.onload = () => {
  8. dialog = document.querySelector('dialog');
  9. dialogPolyfill.registerDialog(dialog);
  10. };

This is the CSS that styles the dialog and the buttons that open and close it.

dialog-demo.css

  1. body {
  2. font-family: sans-serif;
  3. text-align: center;
  4. }
  5.  
  6. /* These are styles for buttons, including
  7.   submit buttons used to close dialogs.
  8.   The background-color and box-shadow styles
  9.   must be specified in order to make the buttons
  10.   look the same across various browser. */
  11. button,
  12. input[type='submit'] {
  13. background-color: white;
  14. border: solid gray 1px;
  15. border-radius: 4px;
  16. box-shadow: none;
  17. margin-top: 10px;
  18. padding: 4px;
  19. }
  20.  
  21. /* These are the styles for the dialog */
  22. dialog {
  23. border: solid gray 2px;
  24. border-radius: 10px;
  25. }
  26.  
  27. /* This is the style for outside the dialog
  28.   when the dialog is displayed
  29.   to make it clear that it is a "modal dialog" and
  30.   the user cannot interact with anything outside it. */
  31. ::backdrop, /* for native <dialog> */
  32. dialog + .backdrop {
  33. /* for dialog-polyfill */
  34. /* a transparent shade of gray */
  35. background-color: rgba(0, 0, 0, 0.2);
  36. }

Example Usage in React

Here is an example of using the <dialog> element in a React application. It was created using create-react-app.

No knowledge of React is required to understand the example.

Edit this file and add the following lines inside the <head> element.

React public/index.html

<link rel="stylesheet" href="dialog-polyfill.css" />
<script src="dialog-polyfill.js"></script>

This is the entire content of App.js, including comments that explain the relevant parts.

React src/App.js

  1. // Tell ESLint that about global variables.
  2. /* global dialogPolyfill: false */
  3.  
  4. import React, {Component} from 'react';
  5. import './App.css';
  6.  
  7. class App extends Component {
  8. // This runs after the App component is rendered.
  9. componentDidMount() {
  10. // Find the dialog using a CSS selector
  11. // and save it for later.
  12. this.dialog = document.querySelector('dialog');
  13.  
  14. // Register the dialog with the polyfill which is
  15. // required by browsers that lack native support.
  16. dialogPolyfill.registerDialog(this.dialog);
  17. }
  18.  
  19. openDialog = () => {
  20. // This is what actually causes the dialog to open.
  21. // By default it will be centered on the page.
  22. this.dialog.showModal();
  23. };
  24.  
  25. render() {
  26. return (
  27. <div className="App">
  28. <!-- This defines the content of the dialog which
  29. can be any HTML, including form elements. -->
  30. <dialog>
  31. I'm a dialog!
  32. <!-- This renders a "Close" button that will
  33. close the dialog when it is clicked. -->
  34. <form method="dialog">
  35. <input type="submit" value="Close" />
  36. </form>
  37. </dialog>
  38.  
  39. <div>This is the page content.</div>
  40.  
  41. <!-- Pressing this button causes the dialog to open. -->
  42. <button onClick={this.openDialog}>Open...</button>
  43. </div>
  44. );
  45. }
  46. }
  47.  
  48. export default App;

React src/App.css

[The content of this file is the same as dialog-demo.css in the previous example.]

Example Usage in React Using Hooks

Here is an example of using the <dialog> element in a reusable React component that uses the relatively new "hooks" feature.

React src/dialog/dialog.js

  1. /*global dialogPolyfill: false */
  2. import {node, object, string} from 'prop-types';
  3. import React, {useEffect} from 'react';
  4. import './dialog.css';
  5. /**
  6.  * Creates a Dialog that can have a title and any content.
  7.  * A ref must be passed via the `dialogRef` prop.
  8.  * This is used by the caller to open and close the dialog.
  9.  * The dialog is initially closed.
  10.  *
  11.  * To open the dialog as a modal, `dialogRef.current.showModal()`.
  12.  * This does not allow interaction with elements outside the dialog.
  13.  *
  14.  * To open the dialog as a non-modal, `dialogRef.current.show()`.
  15.  * This allows interaction with elements outside the dialog.
  16.  *
  17.  * To close the dialog, `dialogRef.current.close()`.
  18.  *
  19.  * @param {node} children - JSX to render in the body of the dialog
  20.  * @param {object} dialogRef - a ref that will be set to refer to the dialog
  21.  * @param {string} title - to display in dialog header
  22.  */
  23. function Dialog({children, dialogRef, title}) {
  24. useEffect(() => {
  25. // Register the dialog with the polyfill which is
  26. // required by browsers that lack native support.
  27. dialogPolyfill.registerDialog(dialogRef.current);
  28. }, []); // only runs once.
  29. return (
  30. <dialog ref={dialogRef}>
  31. <header>
  32. <div className="title">{title}</div>
  33. <button className="close-btn" onClick={() => dialogRef.current.close()}>
  34. </button>
  35. </header>
  36. <section className="body">{children}</section>
  37. </dialog>
  38. );
  39. }
  40. Dialog.propTypes = {
  41. children: node.isRequired,
  42. dialogRef: object.isRequired,
  43. title: string.isRequired
  44. };
  45. export default Dialog;

React src/dialog/dialog.css

  1. dialog {
  2. padding: 0;
  3. }
  4.  
  5. dialog > header {
  6. background-color: white;
  7. border-bottom: solid gray 1px;
  8. display: flex;
  9. justify-content: space-between;
  10. padding: 10px;
  11. }
  12.  
  13. dialog > header > .close-btn {
  14. border: none;
  15. color: gray;
  16. font-size: 24px;
  17. outline: none;
  18. }
  19.  
  20. dialog > header > .title {
  21. color: $green;
  22. font-size: 24px;
  23. font-weight: bold;
  24. margin-right: 20px;
  25. }
  26.  
  27. dialog > .body {
  28. padding: 10px;
  29. }
  30.  
  31. ::backdrop, /* for native <dialog> */
  32. dialog + .backdrop /* for dialog-polyfill */ {
  33. /* a transparent shade of gray */
  34. background-color: rgba(0, 0, 0, 0.4);
  35. }

To use this React component in another functional component:

  1. import React, {useRef} from 'react';
  2. import Dialog from '../dialog/dialog';
  3.  
  4. // Create a ref that will be set to a reference to the dialog.
  5. const dialogRef = useRef();
  6.  
  7. // Render a dialog that will be initially closed.
  8. <Dialog dialogRef={dialogRef} title="some title">
  9. some content
  10. </Dialog>;
  11.  
  12. // To open the dialog,
  13. dialogRef.current.showModal(); // or show() for non-modal
  14.  
  15. // To close the dialog,
  16. dialogRef.current.close();

Example Usage in Vue

Here is an example of using the <dialog> element in a Vue application. It was created using the Vue CLI.

No knowledge of Vue is required to understand the example.

Edit this file and add the following lines inside the <head> element.

Vue public/index.html

<link rel="stylesheet" href="dialog-polyfill.css" />
<script src="dialog-polyfill.js"></script>

Modify the <template> and <script> sections to match the following:

Vue src/App.vue

  1. <template>
  2. <DialogDemo />
  3. </template>
  4.  
  5. <script>
  6. import DialogDemo from './components/DialogDemo.vue';
  7.  
  8. export default {
  9. name: 'app',
  10. components: {
  11. DialogDemo
  12. }
  13. };
  14. </script>

Create this file with the following content:

Vue src/components/DialogDemo.vue

  1. <template>
  2. <div>
  3. <dialog>
  4. I'm a dialog!
  5. <form method="dialog"><input type="submit" value="Close" /></form>
  6. </dialog>
  7. <div>This is page content.</div>
  8. <button @click="dialog.showModal()">Open...</button>
  9. </div>
  10. </template>
  11.  
  12. <script>
  13. /* global dialogPolyfill: false */
  14. export default {
  15. name: 'DialogDemo',
  16. data() {
  17. return {dialog: Object};
  18. },
  19. mounted() {
  20. this.dialog = document.querySelector('dialog');
  21. dialogPolyfill.registerDialog(this.dialog);
  22. }
  23. };
  24. </script>
  25.  
  26. <style>
  27. <!-- Add same CSS rules here as are found in `dialog-demo.css`.
  28. </style>

Summary

Even with the need to use a polyfill, the <dialog> element is very easy to use!

Software Engineering Tech Trends (SETT) is a regular publication featuring emerging trends in software engineering.


secret