Content modal tutorial

Last updated: September 27, 2022.

In this tutorial, we cover how you can create a content modal using HTML, CSS and vanilla JavaScript.

A content modal displays content in a foreground layer in front of the normal website view, usually in response to a user clicking on some content. The normal view is darkened or obscured while the modal is open, bringing the featured content into focus.

In this tutorial, we show you how to create a modal that features an image and descriptive text. But the template can be adapted so that the modal features any type of HTML content.

Let’s get started!

Table of contents

Create a simple modal

To begin, create a container element and nest images inside it. A good source of free images is Unsplash.

Notice that each of the image elements includes a description attribute. This contains the descriptive text that will accompany each image in the modal.

<main id="main">
    <img id="img1" src="images/meadow.jpg" description="Image 1 description">
    <img id="img2" src="images/sea.jpg" description="Image 2 description">
    <img id="img3" src="images/umbrella.jpg" description="Image 3 description">
    <img id="img4" src="images/lighthouse.jpg" description="Image 4 description">
    <img id="img5" src="images/fireworks.jpg" description="Image 5 description">
    <img id="img6" src="images/bike.jpg" description="Image 6 description">
</main>

The above will be the normal page view.

Now, it is time to create the modal, which is initially a non-visible foreground layer (it will be made visible in response to a user click later using JavaScript).

Let’s keep it simple to begin with: the modal will only contain an image and a div element, into which a descriptive text can be inserted.

<div id="modal" class="modal no-display">
    <div id="modal-content-container">
        <img id="modal-image" src="images/bike.jpg">
        <div id="modal-description"></div>
    </div>
</div>

Now, with the CSS, let’s make the modal we just created non-visible and a foreground layer, so when it is made visible, it appears in front of the six images:

.modal {
    z-index: 10;
    width: 100vw;
    height: 100vh;
    left: 0;
    top: 0;
    position: fixed;
    background-color: rgb(0, 0, 0, 0.9);
    display: flex;
    justify-content: center;
    align-items: center;
    margin: 0px;
}

.no-display {
    display: none;
}

#modal-description {
    color: whitesmoke;
    text-align: center;
}

Finally, for the simple modal, we add JavaScript. This is going to do two things.

First, when a user click on one of the six images, the modal will be made visible by removing no-display from the class list. It will also set the src of the modal to match the image clicked.

Second, when a user clicks anywhere on the page while the modal is open, the modal will be closed by adding no-display back to its class list.

const main = document.getElementById('main');
const images = main.getElementsByTagName('img');
const modalImage = document.getElementById('modal-image');
const modalDescription = document.getElementById('modal-description');
 
for(let i=0; i<images.length; i++) {
    images[i].addEventListener('click', function() {
        modalImage.src = images[i].src;
        modalDescription.textContent = images[i].getAttribute('description');
        modal.classList.remove('no-display');
    })
}
 
modal.addEventListener('click', function() {
    modal.classList.add('no-display');
})

Add navigation controls

Rather than closing the modal and opening it again to view another image, it is possible to add user controls within the modal to navigate between content.

For this, the HTML markup needs to be extended to include a navigation container. This should contain at least previous and next buttons. But in this example, we will also be including thumbnails, so a user can see a preview of the next and previous image:

<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.14.0/css/all.min.css" />

<div id="modal" class="modal no-display">
    <div id="modal-content-container">
        <div><span id="close"><i class="fa fa-times" aria-hidden="true"></i></span></div>
        <img id="modal-image" src="images/bike.jpg">
        <div id="modal-description"></div>
        <div id="modal-nav">
            <div id="previous-container">
                <img id="thumb-prev" class="thumb" src="images/bike.jpg">
                <button id="previous" class="modal-nav-button">Previous</button>
            </div>
            <div id="next-container">
                <img id="thumb-next" class="thumb" src="images/bike.jpg">
                <button id="next" class="modal-nav-button">Next</button>
            </div>
        </div>
    </div>
</div>

The CSS styling is also extended to position and style the new elements.

But the important part to make the modal functional remains the same: the modal is set to a non-visible foreground layer initially. And a no-display class exists to toggle its visibility.

.modal {
    z-index: 10;
    width: 100vw;
    height: 100vh;
    left: 0;
    top: 0;
    position: fixed;
    background-color: rgb(0, 0, 0, 0.9);
    display: flex;
    justify-content: center;
    align-items: center;
    margin: 0px;
}

.no-display {
    display: none;
}

.thumb,
.modal-nav-button {
    width: 10rem;
    display: block;
    background-color: purple;
    background: purple;
    color: whitesmoke;
    border: 0px solid;
}

.thumb {
    filter: brightness(50%);
}

.thumb:hover {
    filter: brightness(100%);
}

#close {
    float: right;
    width: auto;
    color: whitesmoke;
    font-size: 1.2rem;
    margin-bottom: 0.2rem;
}

#modal-nav {
    display: flex;
    flex-wrap: nowrap;
    flex-direction: row;
    justify-content: space-around;
    padding-top: 1.5rem;
}

#modal-description {
    color: whitesmoke;
    text-align: center;
}

Finally, the JavaScript is significantly longer. Some features that need to be added:

  • An index that keeps a global track of the current modal image
  • Event listeners added for navigation controls
  • Conditionals to make sure the value of index stays within bounds (length of images)
  • Previous and next thumbnails update to match main modal image
  • Close event listener added to a button (no longer page)

It is good practice to try to solve this yourself.

But in case you are in a hurry, here is a working solution:

const main = document.getElementById('main');
const images = main.getElementsByTagName('img');
const modalImage = document.getElementById('modal-image');
const modal = document.getElementById('modal');
const next = document.getElementById('next');
const previous = document.getElementById('previous');
const close = document.getElementById('close');
const thumbPrev = document.getElementById('thumb-prev');
const thumbNext = document.getElementById('thumb-next');
const prevContainer = document.getElementById('previous-container');
const nextContainer = document.getElementById('next-container');
const modalDescription = document.getElementById('modal-description');

let index;

nextContainer.addEventListener('click', function() {
    index = index+1
    setModal(index);
})

prevContainer.addEventListener('click', function() {
    index=index-1
    setModal(index);
})


for(let i=0; i<images.length; i++) {
    images[i].addEventListener('click', function() {
        index = i;
        setModal(i);
    })
}

function setModal(i) {
    if (i < 0) {
        index = images.length-1
    }
    if (i > images.length-1) {
        index = 0;
    }
    modalImage.src = images[index].src;
    modalDescription.textContent = images[index].getAttribute('description');
    modal.classList.remove('no-display');

    if (index-1 < 0) {
        thumbPrev.src = images[images.length-1].src;
    } else {
        thumbPrev.src = images[index-1].src;
    }

    if (index+1 > images.length-1) {
        thumbNext.src = images[0].src;
    } else {
        thumbNext.src = images[index+1].src;
    }
}

close.addEventListener('click', function() {
    modal.classList.add('no-display');
})

Summary

In this tutorial, we have seen how to create a modal that displays content in greater detail when activated by a user. It is possible for the user to toggle between a page and modal view and navigate through content from inside the modal.

Though we have worked with images with description text as the content of the modal in this tutorial, a modal can be used to display any content that can rendered from HTML.

Feel free to use this code in this tutorial in your own project!