A picture of Penny from Pokémon Scarlet and Violet. She is a girl with glasses and red and blue hair. Arnaught A rainbow tilted to the right.

Adding Photoswipe To My Blog

February 10, 2025 | Tags: personal programming


I have a solution for including images in this blog. It lets me define an image with a source, alt text, and caption (with caption falling back to alt text if it’s missing)[1]. Then, when I want to include an image in the page, I use {% include "picture.html", src: "/assets/image.png", alt: "Image description", caption: "Caption" %}

<figure>
    <a href="{{ src }}" target="_blank">
        <img src="{{ src }}" alt="{{ alt | xml_escape }}" title="{{ alt | xml_escape }}">
    </a>
    <figcaption>{% if caption %}{{ caption }}{% else %}{{ alt }}{% endif %}</figcaption>
</figure>

This method works great, but there are a couple minor pitfalls. Captions and alt text are similar but different things, and if I use a different caption then the alt text isn’t visible anywhere! This isn’t a big deal (the alt text is still available in the location where it needs to be), but I personally like to have the alt text visible.[2] Also, on mobile, the images sometimes appear small, with no way to zoom in.[3]

I wanted to solve this problem by adding an lightbox library. This would solve the problem of zooming in and the alt text and caption being different. When clicking on an image, it will open in a lightbox. This can let you zoom in and show an additional caption. Using this, I can have the caption always visible, and alt text visible after clicking on an image.

Basically, what I want is the lightbox that Sharkey uses. So, I figured I’d just search for “lightbox” in the repo to see what I find. And, it turns out that Sharkey uses Photoswipe!

I looked to see if maybe there’s a plugin or library I can use that ties Photoswipe and 11ty together for me, but as far as I can tell, there isn’t. I did find a blog post[4] contained some helpful information.

Using Photoswipe requires adding 2 JS files, and 1 CSS file, totaling a cool 75.6 KB. It’s not that big. In fact, it’s smaller than some images lol.

A screenshot showing the file size of a PNG image from a previous blog post. It's 2.8 MB!
2.8MB?! (By the way, this image went from 56KB down to 20KB when I converted it to webp)

The format of the picture include template has to change to accommodate Photoswipe. Photoswipe requires images to be encased in an anchor tag, with the href set to the image path, and the width and height in data properties. These are pretty simple requirements that don’t really change all that much about the template. In fact, it makes the template nicer since you can click on an image to open it in a new tab, even without Photoswipe script now!

<figure class="pswp-figure">
    <a href="{{ src }}" data-pswp-width="{{ src | ImageWidth }}" data-pswp-height="{{ src | ImageHeight }}" target="_blank">
        <img src="{{ src }}" alt="{{ alt | xml_escape }}">
    </a>
    <figcaption>{% if caption %}{{ caption }}{% else %}{{ alt }}{% endif %}</figcaption>
</figure>

I added a class (.pswp-figure), so that I can include figures that don’t use Photoswipe if I want. I needed to add a liquid filter to get the width and height. I used sharp to get that done.

eleventyConfig.addLiquidFilter("ImageWidth", async (src) => {
  const metadata = await sharp(src.substring(1)).metadata();

  return metadata.width;
});

eleventyConfig.addLiquidFilter("ImageHeight", async (src) => {
  const metadata = await sharp(src.substring(1)).metadata();

  return metadata.height;
});

Next, to get the alt text caption working, I followed the Photoswipe caption example and copied some of the changes from Sharkey’s captions.

Here is the script and additional styles I’m using to initialize Photoswipe with the alt text caption.

import PhotoSwipeLightBox from "./photoswipe/photoswipe-lightbox.esm.min.js";

const lightbox = new PhotoSwipeLightBox({
  gallery: ".pswp-figure",
  children: "a",
  pswpModule: () => import("./photoswipe/photoswipe.esm.min.js")
});

lightbox.on("uiRegister", function() {
  lightbox.pswp.ui.registerElement({
    name: "alt_text",
    classname: "pswp__alt-text-container",
    appendTo: "wrapper",
    onInit: (el, pswp) => {
      const textbox = document.createElement("p");
      textbox.classList.add("pswp__alt-text-container");
      el.appendChild(textbox);

      lightbox.pswp.on("change", () => {
        const currSlideElement = lightbox.pswp.currSlide.data.element;
        let caption = "";
        if (currSlideElement) {
          caption = currSlideElement.querySelector("img").getAttribute("alt");
          if (!caption) {
            textbox.style.display = "none";
          } else {
            textbox.style.display = "";
          }
        } else {
          textbox.style.display = "none";
        }
        textbox.innerHTML = caption || "";
      });

      const stopEvent = name => textbox.addEventListener(name, event => event.stopPropagation(), { passive: true });
      stopEvent("wheel");
      stopEvent("pointerdown");
      stopEvent("pointercancel");
    }
  });
});

lightbox.init();
.pswp__alt-text-container {
  background: var(--alt-background);
  color: var(--color);
  backdrop-filter: blur(10px);

  text-align: center;
  width: 75%;
  max-width: 800px;
  max-height: 8em;
  padding: .5em 1em;
  border-radius: 0.5em;
  position: absolute;
  left: 50%;
  bottom: 20px;
  transform: translateX(-50%);
  overflow-y: scroll;

  padding: 16px;
}

It took a little bit of work to get set up, but now everything’s working exactly how I want it to. And, it’s a drop in replacement for my previous solution, too!

The only thing that’s gave me trouble is that 11ty (or maybe liquid?) doesn’t like having newlines in alt text. When I put newlines in alt text, it completely breaks the page’s formatting.[5] I know it’s possible[6], but I just can’t get it working with 11ty for whatever reason.

I also went ahead and added Photoswipe to my Mastodon Archive Viewer fork. Now the images in my social media archives are much nicer! You can check it out by going to /lol or /twitter.


  1. Originally, the way I had this originally set up, the caption was always the same as the alt text. ↩︎

  2. For images of text, it makes it possible to search and copy the text. It can also be useful on mobile where images are small. ↩︎

  3. Well, not without opening the image in a new tab, at least. ↩︎

  4. Creating image galleries in eleventy(11ty) with eleventy-img by Prabashwara Seneviratne (bash) ↩︎

  5. I must have encountered this last year, too, because I was already using <br> in my multi-line alt text instead of newlines. ↩︎

  6. I have it working correctly in Mastodon Archive Viewer ↩︎