Building a Fully Functional Modal Without JavaScript

- 3 minutes read

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.

Link to this section 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">
        <meta charset="UTF-8">
        <title>JavaScript-Free Modal</title>
        <link rel="stylesheet" href="modal.css" type="text/css">
        <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>

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.

Link to this section 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;

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: '✖';

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;

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

.modal__toggle:hover {
    cursor: pointer;
    opacity: .8;

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;

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;

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

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

Link to this section 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) => {
   = false;

Link to this section 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.