DEV Community

Nathan
Nathan

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

Building a Fully Functional Modal Without JavaScript

There is a common misconception that in order to create a modal (sometimes also called a dialog), it is necessary to use JavaScript.

In this post, we'll build a JavaScript-free modal.

Using the details tag

HTML has a details tag, which can be used to used to build all kinds of powerful dropdowns and toggle menus.

Let's create a simple HTML file that uses a details tag and summary tag (which is a necessary counterpart to the details tag).

I'm saving mine as index.html:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>JavaScript-Free Modal</title>
        <link rel="stylesheet" href="modal.css" type="text/css">
    </head>
    <body>
        <details class="modal">
            <summary class="modal__toggle"></summary>
            <div class="modal__background">
                <div class="modal__body" tabindex="-1" role="dialog" aria-labelledby="modal__label" aria-live="assertive" aria-modal="true">
                    <p id="modal__label">The modal is open! 🎉</p>
                </div>
            </div>
        </details>
    </body>
</html>
Enter fullscreen mode Exit fullscreen mode

Some of the attributes I've added are important to ensure our modal is accessible and WCAG-compliant: tabindex, role, aria-labelledby, aria-live, and aria-modal.

As you might expect, our modal won't work quite yet, because we haven't added any styles to it.

Styling our modal

The snippet I provided references a CSS file in the same directory called modal.css.

Let's walk through what that file could look like.

First, we'll style the toggle button. The following code uses the [open] attribute to place the <summary> tag, which toggles the modal, atop the modal when it is opened:

@charset "UTF-8";
.modal[open] .modal__toggle {
    left: calc(50vw + 140px);
    top: calc(15vh + 20px);
    position: fixed;
    z-index: 2;
}
.modal[open] .modal__toggle:focus {
    outline: 2px solid #00f;
}
Enter fullscreen mode Exit fullscreen mode

We can then dynamically modify the text within the <summary> tag, depending on whether the modal is open:

.modal__toggle::before {
    content: 'Open Modal';
}
.modal[open] .modal__toggle::before {
    content: '✖';
}
Enter fullscreen mode Exit fullscreen mode

We can also remove the default arrow that browsers add to the left of <summary> tags:

.modal__toggle {
    color: #00f;
    list-style: none;
}
.modal__toggle::-webkit-details-marker {
    display: none;
}
Enter fullscreen mode Exit fullscreen mode

And last but not least, we can improve the hover state of our toggle button:

.modal__toggle:hover {
    cursor: pointer;
    opacity: .8;
}
Enter fullscreen mode Exit fullscreen mode

Next, we'll style the modal background:

.modal__background {
    background-color:rgba(0, 0, 0, .25);
    display: flex;
    height: 100vh;
    justify-content: center;
    left: 0;
    opacity: .8;
    position: fixed;
    top: 0;
    width: 100vw;
    z-index: 1;
}
Enter fullscreen mode Exit fullscreen mode

This background dims the rest of the page, to focus the user's attention on the modal.

Lastly, we'll style the modal body:

.modal__body {
    background-color: #fff;
    box-shadow: 0 0 10px rgba(0, 0, 0, .25);
    font-size: 1.5rem;
    font-weight: 700;
    padding: 40px 20px;
    position: fixed;
    text-align: center;
    top: 15vh;
    width: 300px;
    z-index: 1;
}
Enter fullscreen mode Exit fullscreen mode

That's all! You have a working modal, without JavaScript.

Here is a demo if you want to try it out.

Accessible modals without JavaScript

Our modal has a slight problem.

Because we aren't using JavaScript, we can't listen for whether the user has tried to exit the modal with by pressing ESC.

Our modal is otherwise considered accessible, and this particular feature is not a requirement for a WCAG-compliant modal.

If you don't absolutely have to avoid JavaScript altogether, I'd strongly recommend a simple script to listen for ESC key presses:

document.addEventListener(`keydown`, (e) => {
    if (e.keyCode === 27) {
        document.querySelectorAll(`.modal`).forEach((modal) => {
            modal.open = false;
        });
    }
});

Enter fullscreen mode Exit fullscreen mode

Conclusion

I think it'd be cool to see a widely adopted tag specifically for modals in the spec, especially considering that so many CSS frameworks that wouldn't otherwise need JavaScript use it just for modals.

The dialog tag is an attempt at this, but it still lacks support in several major browsers.

Anyway, this is just one of countless little tricks we can use <details> and <summary> for, and I hope you enjoyed it!

The full code is available as a repository on my GitHub account.

Discussion (0)