Making images smaller and giving them lightbox effects will help visitors linger on your site and it'll also move info up on the page, where people have greater attention.

Intro

The internet is full of memes and videos showing kids spooked by jack-in-the-box toys.

What these fail to represent, though, are the hours and hours those same kids spend reloading and replaying the toys after they overcome their initial surprise and distrust. It turns out that we're all really fascinated by things popping out at us, as long as we're expecting it.

Lightboxes are the web equivalent of jack-in-the-box toys. As long as you use them well, they can drive interaction and engagement in your site. If misused, though, or implemented poorly, they can be a significant turn off, or even introduce accessibility noncompliance issues.

Text Considerations

We all know that images are essential to the web. Gone are the days of webpages that are just walls of text (and thank goodness!). Images make the content engaging and digestible. They draw you in, and also give your mind a break from the difficult language processing required by text.

An image is indeed worth a thousand words. But an image can also easily fill the equivalent space of a thousand words, or at least a couple hundred.

The words are what you use on your website to announce your definite purpose. Without the pictures, people may not stay on your site long enough to buy, for example, but without your words, they may not even know that the point of your site is for them to buy.

The benefits of images (their almost instantaneous connection for people), and their detriments (displacing significant amounts of text that could be used to more clearly guide visitors in their journey through your site) need to be balanced. All of this needs to also take into account the fact that visitors' attention lags in direct proportion to the distance they've scrolled down the page.

Dangers of Scrolling

The Norman Nielsen Group (NN Group), usability experts, found that people aren't averse to scrolling, as was once believed. The standard thought used to be that everything of value needed to be "above the fold", or in other words, in the first screen-view of the page, because people won't scroll down to see the rest.

NN Group smashed this idea with the results of their study, finding that people do generally scroll all the way to the bottom of long pages. The time they spend in the top two screens' worth of the page, however, is dramatically more than in the rest of it, to the tune of 74% of time spent there vs 26% spent in the rest, even if there are three or four more screenfuls below. It seems we tend to spend about three quarters of our time in the top two screenfuls, and split the rest of our time amongst the others.

Because of this tendency for visitors to spend less time viewing content the further down they scroll, it is a good idea to economize space higher up on your page. In Slippy Sliders I hypothesized that carousel sliders have become popular with website owners in part to account for this user behavior. Owners want to cram as much content up top as possible.

Despite this inclination, I hope that in that post I effectively dispelled the myth that sliders are a good idea for any use-case. My firm opinion is they should never be used for anything, except maybe hiding content that you really don't want anyone to see.

One way to economize space that I brought up in that post, but didn't dwell on, was to use a lightbox effect on image galleries, and even on individual images or sections. A lightbox effect is when by clicking on the thumbnail of an image, you expand it to fill the screen in a same-browser popup.

Yes, this is the same effect that's used by those annoying newsletter-signup popups that trigger automatically on some people's sites, but those aren't what I'm talking about.

I'm talking about visitor-initiated lightboxes that disable the screen beneath them only at the visitor's request, giving them a better view of what, to save space, can now be a fairly small image. Thus instead of taking the space of a couple hundred words, maybe as a lightbox thumbnail, it only takes the space of a couple dozen, allowing you to gracefully fit more information on the first two screens, and really everywhere on your page.

Increase interaction

In addition to economy, making your images into user-initiated lightboxes can potentially increase your visitors' involvement in your site. Interactive websites, according to Forbes, "are typically more engaging, which can lead to more conversions."

Just like some kids will entertain themselves for hours popping the jack-in-the-box open again and again, some visitors will expand each of your lightboxes simply because they can.

Giving your visitors the option to do something to your images, even if it's just clicking to expand them, increases the interactivity of your site. It might be even better if you make it easy for them to download that image or share it on social media. All of these things add engagement to your site in a meaningful way.

Before you go crazy adding a hundred postage-stamp-sized image thumbnail lightbox triggers to your pages, let's discuss really quickly the optimal use of images. The Norman Nielsen Group again comes to the rescue with some great recommendations about what kind of images are worth including. They say that decorative images are ignored while content images are scrutinized.

I believe the data they present: visitors spend more time looking at contentful images than decorative ones. I somewhat disagree with NN Group's conclusion that because of this fact, decorative images should be avoided. People may not spend as much time looking at decorative as contentful images, or even as the text content of the site, but studies have shown that images can be processed in as little as 13 milliseconds, so even if the eye being tracked by the NN Group doesn't pause on an image, that doesn't mean the brain behind the eye isn't processing it and making snap judgments.

I think it's likely that even the images that seem purely decorative contribute significantly to the general feel and emotional draw of the site. Besides, as I said earlier, I think the small cognitive rest an image offers visitors is also valuable. Still, the advice to fill your site with more contentful images than decorative ones is sound. You should add as many images of your actual staff, and actual products being used, and so forth, as possible.

My further advice for images is that you should be certain to make them accessible. The way to do this, as I explored in Seeing is Believing Part 3, is to include alt text. This alternative text will make it so all your visitors can have an equivalent experience with your site, even if they can't see your images.

You can technically omit the alt text on purely decorative images (though they should still have an empty alt attribute, or in other words, alt=""). It's my opinion, though, that there shouldn't be very many of those. If decorative images contribute to the feel or vibe of your site, I think you should write some alt text for those that can pass on the same vibe to blind visitors.

One of the NN Group's recommendations that I wholeheartedly agree with is that when a visitor requests a larger version of an image (like by activating a lightbox trigger), it should be given to them much bigger. Twenty or thirty-percent larger won't cut it, it should be two or three times larger, or more. This is easy to achieve with a lightbox image as I've recommended, and you can check out some simple and effective code to make it possible in the appendix to this post.

Lightboxes: what not to do

Before giving you my recommendation for how to add lightbox images to your site, let me first caution you about what you shouldn't do. Don't go out to the WordPress plugin directory or the Shopify app store and just install the lightbox gallery plugin that looks coolest to you.

Unfortunately, most of these cool lightbox plugins and apps will introduce pretty significant accessibility issues to your site, and thus should be avoided, or at least remediated so their accessibility problems don't become your accessibility problems. You've done a great job following all the advice in our Accessibility series and don't want to blow it over some slick-looking-but-problematic plugin.

Lightbox accessibility

I went through a lot of the plugins out there, and found that most of them had some or all of these pretty significant accessibility issues:

  1. They may not activate with keyboard, or return to the original thumbnail on escape or close. This interactive behavior should be the bare minimum, and work for all users. If there are interactive elements on the page, all users need to be able to activate them.

    Let's consider this from the perspective of someone who doesn't have enough fine-motor control to use a mouse, so they move around the website by tabbing with their keyboard, and activate interactive elements by using the Enter key. If they see there's a lightbox image, but can't activate it, that would be very frustrating, and even discriminatory. This could also be the case if it's someone with poor vision who mostly uses a screen reader to get around the internet.

  2. On activation, the lightboxes may not capture focus. This is a problem for keyboard users where they could still tab around the various elements underneath the lightbox if their focus doesn't go to the interactive elements in the lightbox itself.

    Furthermore, if on closing the lightbox the focus doesn't return to the trigger, in this case the thumbnail image, this is very frustrating. If, for example, every time users close the lightboxes with the close button or by hitting Escape, their focus returns to the top of the page, you can imagine the aggravation of having to tab all the way back through the page and the gallery. Unfortunately, that's exactly how I saw many of the lightbox plugins function.

  3. There may be no point for screen reader users to activate them at all. If the lightbox images have any alt text, it's probably the same as on the thumbnail. This means that even assuming screen reader users can trigger the lightbox, there'd really be no point for them to do so because they'd just get the same information as they would on the thumbnail image. I suspect screen reader users expect this, so my guess is that they largely ignore image lightboxes, though I haven't found any research to confirm my suspicion.

  4. They may be resource-heavy, and so take a long time to load. This isn't an accessibility concern, really, but should still be a business concern for every website. Many lightbox galleries make use of a lot of heavy code to enable their functionality, even if they have web-optimized image files, which they probably often don't.

Since I wasn't able to find any Wordpress plugins or sample code that overcame these four problems, I devised some code myself that you're free to use as you will. My code doesn't have as many neat features as some plugins or apps, but it has the advantage of being based on native html dialog elements, which have basic lightbox functionality built right into them.

I feel like if there's a native element that does what we're looking for, we should use it. That keeps the web as semantic as possible, which has all sorts of benefits. You'll find the code to my native dialog-based lightbox gallery in the appendix to this post, right after a demonstration of its implementation.

Conclusion

You should definitely include images in your site to give your visitors a break from your text, and to establish the feel of your site that you're looking for. At the same time, you should be aware that pushing your text down past a screen or two will result in less visitor attention the farther down it goes. Mitigate this problem by shrinking your images in size and giving them a visitor-initiated lightbox effect. Doing this will increase visitor interaction with your site and help you better accomplish your purposes.

Appendix: DIALOG modal gallery

The commentary

All of those images are working as a gallery above, but they'll also work individually if you put them and the javascript code on a regular page, wherever you put the button that contains the image. Check it out in this text section:

All you need to do is make sure the syntax is correct:

  1. The lightbox image should be contained in a button.
  2. The button should have a class of "lightbox-trigger".
  3. The button can have an optional attribute of "position" that determines where the focus of the photo will be when expanded: "top", "bottom", "left", or "right".
  4. The image should have a brief alt attribute of no more than about 125 characters.
  5. Following the image, inside the button, there should be a paragraph containing a more detailed description of the image for screen reader users who open the lightbox, and another paragraph giving the credit of the image.
  6. The paragraph elements should have aria-hidden attributes of "true".
  7. The first paragraph element should also have classes of "sr-only description", and the text of this paragraph should be a detailed description of the image.
  8. The second paragraph should have classes of "sr-only credit". The text of this paragraph should be the credit attribution of the image, giving visitors enough information that they can find it through an internet search. I recommend at least the creator of the image and the site where it's hosted (Pixabay, Unsplash, Wikimedia, etc).

That's it for the html! Add the javascript code to the end of the BODY of your page, and it will manage the creation of the dialog elements that natively handle the rest of the functionality of the lightboxes.

The styles should be put in your stylesheet in case you want to utilize lightboxes elsewhere in your site, but the javascript should probably be added on a page-by-page basis as needed. One hundred eighty-nine lines of javascript won't slow things down too much if you do want to include them in your main javascript file to be loaded throughout your site, but to keep things snappiest I recommend only loading it where needed.

Notes:

  • I recommend you keep the image alt text quite short, restricting yourself to more or less the title of the image. Put a lengthier description in the aria-hidden paragraph below the image. This is pushed into the lightbox and unhidden for screen reader users once they activate the lightbox, giving them a better understanding of the image, similar to how sighted users can see more details on their expanded lightbox image.
  • If you want to include the download link, be aware that it will only trigger an automatic download if you've got the image file on the same server as your webpage. Otherwise the browser will open a page of the full version of your image and your visitors can right-click to download manually from there.

The code

Commented code for learning

Commented HTML (simple lightbox)


<button class="lightbox-trigger" position="top" > <!-- if you want the lightbox image to be scrolled to top, bottom, left, or right, specify it here -->
    <img loading="lazy" src="URL-to-image-file" alt="Simple descriptor"> <!-- keep your alt text basic, like the name of the image -->
    <p aria-hidden="true" class="sr-only description">[add your expanded image description here]</p>
    <p aria-hidden="true" class="sr-only credit">[add your image credit here]</p>
</button>
            

Commented HTML (lightbox gallery)


<div class="gallery">
    <button class="lightbox-trigger" >
        <img loading="lazy" src="URL-to-image-file" alt="Simple descriptor"> 
        <p aria-hidden="true" class="sr-only description">[add your expanded image description here]</p>
        <p aria-hidden="true" class="sr-only credit">[add your image credit here]</p>
    </button>
    <button class="lightbox-trigger" >
        <img loading="lazy" src="URL-to-image-file" alt="Simple descriptor"> 
        <p aria-hidden="true" class="sr-only description">[add your expanded image description here]</p>
        <p aria-hidden="true" class="sr-only credit">[add your image credit here]</p>
    </button>
    <button class="lightbox-trigger" >
        <img loading="lazy" src="URL-to-image-file" alt="Simple descriptor"> 
        <p aria-hidden="true" class="sr-only description">[add your expanded image description here]</p>
        <p aria-hidden="true" class="sr-only credit">[add your image credit here]</p>
    </button>
</div>
            

Commented javascript


    window.addEventListener('load', function() {
        // set variables
        let triggers = document.querySelectorAll(".lightbox-trigger");
        let triggerImages = document.querySelectorAll(".lightbox-trigger img");
        let imageDescriptions = document.querySelectorAll(".lightbox-trigger .description");
        let imageCredits = document.querySelectorAll(".lightbox-trigger .credit");
        let srNote = document.createElement("div");
        let createdDialogs = {};
        let createdDownloadButtons = {};
        let createdCloseButtons = {};
        let imageURLs = {};
    
        // add screen reader clarification for trigger links
        srNote.textContent = "Click for details";
        srNote.classList = "sr-only";
        srNote.setAttribute("aria-hidden","true")
        srNote.id = "sr-note";
        triggers[0].append(srNote);
    
        // button creation function
        function createButton(name,title,label,href,target,rel,attribute) {
            name = document.createElement("a");
            if (title === "download"){
                name.classList = "gallery-button";
                name.classList.add("download");
            } else {
                name.classList = "gallery-button";
                name.classList.add(title+"-share")
            } ;
            name.textContent = label;
            if (href) {
                name.href = href;
            }
            if (target) {
                name.target = "_blank";
            };
            if (rel) {
                name.rel = "noopener";
            };
            if (attribute) {
                name.setAttribute("download","true");
            }
            return name;
        }
    
        // Function to load an image and return a promise
        function loadImage(src, index) {
            return new Promise((resolve, reject) => {
                const img = new Image();
                img.onload = () => {
                    resolve(img);
                };
                img.onerror = () => {
                    reject(new Error(`Failed to load image ${index}`));
                };
                img.src = src;
            });
        }
    
        // Create an array to hold all image loading promises
        let imageLoadPromises = [];
    
        // Prepare image loading promises for all triggers
        for (let i = 0; i < triggers.length; i++) {
            imageURLs[i] = triggerImages[i].src;
            imageLoadPromises.push(loadImage(imageURLs[i], i));
        }
    
        // Wait for all images to load before creating dialogs
        Promise.all(imageLoadPromises)
            .then(loadedImages => {
                loadedImages.forEach((img, i) => {
                    let imageWidth = img.naturalWidth;
                    let imageHeight = img.naturalHeight;
                    let imageAspectRatio = imageWidth / imageHeight;
                    let imageURL = imageURLs[i];
    
                    // add visual "click to expand" label beneath each image
                    let visualLabel = document.createElement("div");
                    visualLabel.textContent = "Click to expand";
                    visualLabel.classList = "lightbox-label";
                    visualLabel.setAttribute("aria-hidden","true");
                    triggers[i].append(visualLabel);
            
                    // give each trigger link a descriptive accessible name for screen-reader visitors
                    triggers[i].id = "photo"+i;
                    triggers[i].setAttribute("aria-labelledby","photo"+i+" sr-note");
    
                    // dynamically create the native dialog elements
                    let createdDialog = document.createElement("dialog");
                    createdDialog.classList = "dialog-lightbox";
                    createdDialog.setAttribute("autofocus","");
                    let createdDialogDiv = document.createElement("div");
                    createdDialogDiv.classList = "dialog-image";
    
                    // set default background-image position of expanded lightbox image
                    let position = "center";
                    // allow position to be set with optional attribute on trigger link
                    if (triggers[i].hasAttribute("position")) {
                        position = triggers[i].getAttribute("position");
                    }
    
                    let createdDialogDivStyle = `
                        background-image: url('${imageURL}');
                        background-size: cover;
                        background-position: ${position};
                        background-color: #ccc; /* Fallback color */
                    `;
                    
                    if (imageAspectRatio > 1) {
                        createdDialogDivStyle += `width: calc(88vh*${imageAspectRatio}); height: 88vh;`;
                    } else if (imageAspectRatio < 1) {
                        createdDialogDivStyle += `width: 88vw; height: calc(88vw/${imageAspectRatio});`;
                    }
                    
                    createdDialogDiv.style = createdDialogDivStyle;
    
                    // parrot image descriptions for screen readers
                    let createdImageDescription = document.createElement("span");
                    createdImageDescription.classList = "sr-only";
                    createdImageDescription.textContent = imageDescriptions[i].textContent;
                    let createdCloseButton = document.createElement("button");
                    createdCloseButton.classList = "close gallery-button";
                    createdCloseButton.textContent ="X";
                    createdCloseButton.setAttribute("aria-label","Close");
                    createdDialog.append(createdImageDescription,createdCloseButton);
                    
                    // make a div to hold the lightbox buttons
                    let createdDialogButtons = document.createElement("div");
                    createdDialogButtons.classList = "gallery-buttons";
    
                    createdDialog.append(createdDialogButtons,createdDialogDiv);
                    let createdDownloadButton = createButton("download","download","Download Image",imageURLs[i],"","","download");
                    let createdLinkedInButton = createButton("linkedin","linkedin","Share on LinkedIn","http://www.linkedin.com/shareArticle?mini=true&url="+imageURLs[i],"_blank","noopener","");
                    let createdFacebookButton = createButton("facebook","facebook","Share on Facebook","https://www.facebook.com/sharer/sharer.php?u="+imageURLs[i],"_blank","noopener","");
                    let createdDialogCredit = document.createElement("span");
                    createdDialogCredit.classList = "image-credit";
                    createdDialogCredit.textContent = imageCredits[i].textContent;
                    if (createdDialogCredit.textContent.length === 0) {
                        createdDialogButtons.append(createdDownloadButton,createdLinkedInButton,createdFacebookButton);
                    } else {
                        createdDialogButtons.append(createdDownloadButton,createdLinkedInButton,createdFacebookButton,createdDialogCredit);
                    }
    
                    // add each dialog element to the page
                    document.body.append(createdDialog);
    
                    // set the thumbnail image links to open their lightboxes on click
                    triggers[i].addEventListener("click", () => {
                        createdDialog.showModal();
                        // Use requestAnimationFrame to ensure the dialog is rendered
                        requestAnimationFrame(() => {
                            const position = triggers[i].getAttribute("position") || "center";
                            centerImage(createdDialogDiv, position);
                        });
                        document.addEventListener('keydown', function(event) {
                            if (event.key === 'Escape') {
                                createdDialog.close();
                                triggers[i].scrollIntoView({ behavior: "instant", block: "center", inline: "nearest" });
                            }
                        });
                    });
    
                    // make the close button close the lightbox on click
                    createdCloseButton.addEventListener("click", () => {
                        createdDialog.close();
                        triggers[i].scrollIntoView({ behavior: "instant", block: "center", inline: "nearest" });
                    });
                    // make lightbox close if click outside of it
                    createdDialog.addEventListener('mousedown', event => {
                        if (event.target === event.currentTarget) {
                            createdDialog.close();
                            triggers[i].focus();
                            // event.currentTarget.close()
                            triggers[i].scrollIntoView({ behavior: "instant", block: "center", inline: "nearest" });
                        }
                    })
                });
            })
            .catch(error => {
                // This error logging is kept for critical errors
                console.error("Error in image loading process:", error);
            });
    
        // Function to center or position the image
        function centerImage(dialogDiv, position = "center") {
            const dialogRect = dialogDiv.getBoundingClientRect();
            const viewportWidth = window.innerWidth;
            const viewportHeight = window.innerHeight;
    
            let scrollX, scrollY;
    
            switch (position.toLowerCase()) {
                case "top":
                    scrollX = (dialogRect.width - viewportWidth) / 2;
                    scrollY = 0;
                    break;
                case "bottom":
                    scrollX = (dialogRect.width - viewportWidth) / 2;
                    scrollY = Math.max(0, dialogRect.height - viewportHeight);
                    break;
                case "left":
                    scrollX = 0;
                    scrollY = (dialogRect.height - viewportHeight) / 2;
                    break;
                case "right":
                    scrollX = Math.max(0, dialogRect.width - viewportWidth);
                    scrollY = (dialogRect.height - viewportHeight) / 2;
                    break;
                default: // center
                    scrollX = (dialogRect.width - viewportWidth) / 2;
                    scrollY = (dialogRect.height - viewportHeight) / 2;
            }
    
            // Ensure scroll values are not negative
            scrollX = Math.max(0, scrollX);
            scrollY = Math.max(0, scrollY);
    
            // Use the dialog element for scrolling, not the inner div
            const dialog = dialogDiv.closest('dialog');
            dialog.scrollLeft = scrollX;
            dialog.scrollTop = scrollY;
        }
    });
            

Commented CSS


    .dialog-lightbox {
        border: 0;
        padding: 0;
    }
    .lightbox-trigger {
    margin: 5px;
    padding: 0;
    display: inline-block;
    height: 200px;
    cursor: pointer;
    transition: transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
    }
    .lightbox-trigger:hover,
    .lightbox-trigger:focus {
        transform: scale(1.05);
        animation: bounce 0.5s ease-in-out;
    }
    @keyframes bounce {
        0%, 100% {
            transform: scale(1.05);
        }
        50% {
            transform: scale(1.08);
        }
    }
    .lightbox-trigger img {
        height: 200px;
    }
    .lightbox-label,
    .gallery-button,
    .close{
        background: #000;
        color: #fff;
        display: block;
        padding: 2px;
        border: solid #000;
        text-transform: uppercase;
        font-family:Arial, Helvetica, sans-serif;
        font-size: 12px;
        text-align: center;
        text-decoration: none;
    }
    .gallery-button {
        font-size: 14px;
        margin: 4px 0 4px 0;
    }
    .dialog-lightbox::backdrop {
        background: #000000BF;
    }
    .gallery-buttons {
        position: fixed;
        opacity: .55;
        height: 85vh;
    }
    .gallery-buttons:focus-within,
    .gallery-buttons:hover {
        opacity: 1
    }
    .close {
        position: fixed;
        top: 0px;
        left: 5px;
    }
    .gallery-button:hover,
    .close:hover {
        background: #000;
        color: #fff;
        cursor: pointer;
    }
    .image-div {
        margin-top: 12px;
    }
    .gallery img {
        height: 200px;
    }
    span.image-credit {
        display: block;
        border: solid 1px;
        margin-top: 5px;
        padding: 5px;
        font-size: 12px;
        position: absolute;
        bottom: 0;
        background: #fff;
    }
    @media screen and (min-width: 700px) {
        span.image-credit {
            display: inline-block;
            margin-top: 0;
        }
    }
    /* if your site already has an sr-only class, you can omit this */
    .sr-only {
        border: 0 !important;
        clip: rect(1px, 1px, 1px, 1px) !important;
        -webkit-clip-path: inset(50%) !important;
            clip-path: inset(50%) !important;  
        height: 1px !important;
        margin: -1px !important;
        overflow: hidden !important;
        padding: 0 !important;
        position: absolute !important;
        width: 1px !important;
        white-space: nowrap !important;            
    }
            
Uncommented code for production

Uncommented HTML (simple lightbox)


<button class="lightbox-trigger" position="top" >
    <img loading="lazy" src="URL-to-image-file" alt="Simple descriptor"> 
    <p aria-hidden="true" class="sr-only description">[add your expanded image description here]</p>
    <p aria-hidden="true" class="sr-only credit">[add your image credit here]</p>
</button>
            

Uncommented HTML (lightbox gallery)


<div class="gallery">
    <button class="lightbox-trigger" >
        <img loading="lazy" src="URL-to-image-file" alt="Simple descriptor"> 
        <p aria-hidden="true" class="sr-only description">[add your expanded image description here]</p>
        <p aria-hidden="true" class="sr-only credit">[add your image credit here]</p>
    </button>
    <button class="lightbox-trigger" >
        <img loading="lazy" src="URL-to-image-file" alt="Simple descriptor"> 
        <p aria-hidden="true" class="sr-only description">[add your expanded image description here]</p>
        <p aria-hidden="true" class="sr-only credit">[add your image credit here]</p>
    </button>
    <button class="lightbox-trigger" >
        <img loading="lazy" src="URL-to-image-file" alt="Simple descriptor"> 
        <p aria-hidden="true" class="sr-only description">[add your expanded image description here]</p>
        <p aria-hidden="true" class="sr-only credit">[add your image credit here]</p>
    </button>
</div>
            

Uncommented javascript


    window.addEventListener('load', function() {
        let triggers = document.querySelectorAll(".lightbox-trigger");
        let triggerImages = document.querySelectorAll(".lightbox-trigger img");
        let imageDescriptions = document.querySelectorAll(".lightbox-trigger .description");
        let imageCredits = document.querySelectorAll(".lightbox-trigger .credit");
        let srNote = document.createElement("div");
        let createdDialogs = {};
        let createdDownloadButtons = {};
        let createdCloseButtons = {};
        let imageURLs = {};
    
        srNote.textContent = "Click for details";
        srNote.classList = "sr-only";
        srNote.setAttribute("aria-hidden","true")
        srNote.id = "sr-note";
        triggers[0].append(srNote);
    
        function createButton(name,title,label,href,target,rel,attribute) {
            name = document.createElement("a");
            if (title === "download"){
                name.classList = "gallery-button";
                name.classList.add("download");
            } else {
                name.classList = "gallery-button";
                name.classList.add(title+"-share")
            } ;
            name.textContent = label;
            if (href) {
                name.href = href;
            }
            if (target) {
                name.target = "_blank";
            };
            if (rel) {
                name.rel = "noopener";
            };
            if (attribute) {
                name.setAttribute("download","true");
            }
            return name;
        }
    
        function loadImage(src, index) {
            return new Promise((resolve, reject) => {
                const img = new Image();
                img.onload = () => {
                    resolve(img);
                };
                img.onerror = () => {
                    reject(new Error(`Failed to load image ${index}`));
                };
                img.src = src;
            });
        }
    
        let imageLoadPromises = [];
    
        for (let i = 0; i < triggers.length; i++) {
            imageURLs[i] = triggerImages[i].src;
            imageLoadPromises.push(loadImage(imageURLs[i], i));
        }
    
        Promise.all(imageLoadPromises)
            .then(loadedImages => {
                loadedImages.forEach((img, i) => {
                    let imageWidth = img.naturalWidth;
                    let imageHeight = img.naturalHeight;
                    let imageAspectRatio = imageWidth / imageHeight;
                    let imageURL = imageURLs[i];
    
                    let visualLabel = document.createElement("div");
                    visualLabel.textContent = "Click to expand";
                    visualLabel.classList = "lightbox-label";
                    visualLabel.setAttribute("aria-hidden","true");
                    triggers[i].append(visualLabel);
            
                    triggers[i].id = "photo"+i;
                    triggers[i].setAttribute("aria-labelledby","photo"+i+" sr-note");
    
                    let createdDialog = document.createElement("dialog");
                    createdDialog.classList = "dialog-lightbox";
                    createdDialog.setAttribute("autofocus","");
                    let createdDialogDiv = document.createElement("div");
                    createdDialogDiv.classList = "dialog-image";
    
                    let position = "center";
                    if (triggers[i].hasAttribute("position")) {
                        position = triggers[i].getAttribute("position");
                    }
    
                    let createdDialogDivStyle = `
                        background-image: url('${imageURL}');
                        background-size: cover;
                        background-position: ${position};
                        background-color: #ccc; /* Fallback color */
                    `;
                    
                    if (imageAspectRatio > 1) {
                        createdDialogDivStyle += `width: calc(88vh*${imageAspectRatio}); height: 88vh;`;
                    } else if (imageAspectRatio < 1) {
                        createdDialogDivStyle += `width: 88vw; height: calc(88vw/${imageAspectRatio});`;
                    }
                    
                    createdDialogDiv.style = createdDialogDivStyle;
    
                    let createdImageDescription = document.createElement("span");
                    createdImageDescription.classList = "sr-only";
                    createdImageDescription.textContent = imageDescriptions[i].textContent;
                    let createdCloseButton = document.createElement("button");
                    createdCloseButton.classList = "close gallery-button";
                    createdCloseButton.textContent ="X";
                    createdCloseButton.setAttribute("aria-label","Close");
                    createdDialog.append(createdImageDescription,createdCloseButton);
                    
                    let createdDialogButtons = document.createElement("div");
                    createdDialogButtons.classList = "gallery-buttons";
    
                    createdDialog.append(createdDialogButtons,createdDialogDiv);
                    let createdDownloadButton = createButton("download","download","Download Image",imageURLs[i],"","","download");
                    let createdLinkedInButton = createButton("linkedin","linkedin","Share on LinkedIn","http://www.linkedin.com/shareArticle?mini=true&url="+imageURLs[i],"_blank","noopener","");
                    let createdFacebookButton = createButton("facebook","facebook","Share on Facebook","https://www.facebook.com/sharer/sharer.php?u="+imageURLs[i],"_blank","noopener","");
                    let createdDialogCredit = document.createElement("span");
                    createdDialogCredit.classList = "image-credit";
                    createdDialogCredit.textContent = imageCredits[i].textContent;
                    if (createdDialogCredit.textContent.length === 0) {
                        createdDialogButtons.append(createdDownloadButton,createdLinkedInButton,createdFacebookButton);
                    } else {
                        createdDialogButtons.append(createdDownloadButton,createdLinkedInButton,createdFacebookButton,createdDialogCredit);
                    }
    
                    document.body.append(createdDialog);
    
                    triggers[i].addEventListener("click", () => {
                        createdDialog.showModal();
                        requestAnimationFrame(() => {
                            const position = triggers[i].getAttribute("position") || "center";
                            centerImage(createdDialogDiv, position);
                        });
                        document.addEventListener('keydown', function(event) {
                            if (event.key === 'Escape') {
                                createdDialog.close();
                                triggers[i].scrollIntoView({ behavior: "instant", block: "center", inline: "nearest" });
                            }
                        });
                    });
    
                    createdCloseButton.addEventListener("click", () => {
                        createdDialog.close();
                        triggers[i].scrollIntoView({ behavior: "instant", block: "center", inline: "nearest" });
                    });
                    createdDialog.addEventListener('mousedown', event => {
                        if (event.target === event.currentTarget) {
                            createdDialog.close();
                            triggers[i].focus();
                            triggers[i].scrollIntoView({ behavior: "instant", block: "center", inline: "nearest" });
                        }
                    })
                });
            })
            .catch(error => {
                console.error("Error in image loading process:", error);
            });
    
        function centerImage(dialogDiv, position = "center") {
            const dialogRect = dialogDiv.getBoundingClientRect();
            const viewportWidth = window.innerWidth;
            const viewportHeight = window.innerHeight;
    
            let scrollX, scrollY;
    
            switch (position.toLowerCase()) {
                case "top":
                    scrollX = (dialogRect.width - viewportWidth) / 2;
                    scrollY = 0;
                    break;
                case "bottom":
                    scrollX = (dialogRect.width - viewportWidth) / 2;
                    scrollY = Math.max(0, dialogRect.height - viewportHeight);
                    break;
                case "left":
                    scrollX = 0;
                    scrollY = (dialogRect.height - viewportHeight) / 2;
                    break;
                case "right":
                    scrollX = Math.max(0, dialogRect.width - viewportWidth);
                    scrollY = (dialogRect.height - viewportHeight) / 2;
                    break;
                default: // center
                    scrollX = (dialogRect.width - viewportWidth) / 2;
                    scrollY = (dialogRect.height - viewportHeight) / 2;
            }
    
            scrollX = Math.max(0, scrollX);
            scrollY = Math.max(0, scrollY);
    
            const dialog = dialogDiv.closest('dialog');
            dialog.scrollLeft = scrollX;
            dialog.scrollTop = scrollY;
        }
    });
            

CSS


    .dialog-lightbox {
        border: 0;
        padding: 0;
    }
    .lightbox-trigger {
    margin: 5px;
    padding: 0;
    display: inline-block;
    height: 200px;
    cursor: pointer;
    transition: transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
    }
    .lightbox-trigger:hover,
    .lightbox-trigger:focus {
        transform: scale(1.05);
        animation: bounce 0.5s ease-in-out;
    }
    @keyframes bounce {
        0%, 100% {
            transform: scale(1.05);
        }
        50% {
            transform: scale(1.08);
        }
    }
    .lightbox-trigger img {
        height: 200px;
    }
    .lightbox-label,
    .gallery-button,
    .close{
        background: #000;
        color: #fff;
        display: block;
        padding: 2px;
        border: solid #000;
        text-transform: uppercase;
        font-family:Arial, Helvetica, sans-serif;
        font-size: 12px;
        text-align: center;
        text-decoration: none;
    }
    .gallery-button {
        font-size: 14px;
        margin: 4px 0 4px 0;
    }
    .dialog-lightbox::backdrop {
        background: #000000BF;
    }
    .gallery-buttons {
        position: fixed;
        opacity: .55;
        height: 85vh;
    }
    .gallery-buttons:focus-within,
    .gallery-buttons:hover {
        opacity: 1
    }
    .close {
        position: fixed;
        top: 0px;
        left: 5px;
    }
    .gallery-button:hover,
    .close:hover {
        background: #000;
        color: #fff;
        cursor: pointer;
    }
    .image-div {
        margin-top: 12px;
    }
    .gallery img {
        height: 200px;
    }
    span.image-credit {
        display: block;
        border: solid 1px;
        margin-top: 5px;
        padding: 5px;
        font-size: 12px;
        position: absolute;
        background: #fff;
        bottom: 0;
    }
    @media screen and (min-width: 700px) {
        span.image-credit {
            display: inline-block;
            margin-top: 0;
        }
    }
    .sr-only {
        border: 0 !important;
        clip: rect(1px, 1px, 1px, 1px) !important;
        -webkit-clip-path: inset(50%) !important;
            clip-path: inset(50%) !important;  
        height: 1px !important;
        margin: -1px !important;
        overflow: hidden !important;
        padding: 0 !important;
        position: absolute !important;
        width: 1px !important;
        white-space: nowrap !important;            
    }
            

Want a little help converting those images to lightboxes? Or selecting images, or making them accessible? Contact us for a consultation and we'll help you make sure they pop.