Skip to main content

/ Treasa Ní Chonchúir

Styling empty <figure> elements

HTML’s <figure> element is a really nice way to associate a caption with some content. It’s really easy to use! You simply put the content that needs captioning (an <img>, say) inside a <figure> element, and then you add a <figcaption> element containing the caption:

<figure>
    <img src=something alt=blahblahblah>
    <figcaption>
        blah blah blah
    </figcaption>
</figure>

The caption can come before or after the content it’s captioning. This is also okay:

<figure>
    <figcaption>
        blah blah blah
    </figcaption>
    <img src=something alt=blahblahblah>
</figure>

When I’m writing a post, sometimes I don’t yet have the images I intend to include in it. I usually know something about the image, though, so I often end up writing <figure> elements that only have <figcaption> children:

<figure>
    <figcaption>
        blah blah blah
    </figcaption>
</figure>

In addition to being invalid, this can end up looking pretty weird if the site’s layout assumes figures have non-caption content. So I used to add a <div> to stand in for the image, like so:

<figure>
    <div class=placeholder></div>
    <figcaption>
        Something about the image.
    </figcaption>
</figure>

I’d then set the <div>’s background image to be the image placeholder SVG I created for my site:

A placeholder image. It’s a rounded rectangle with a squiggly line and a cirle in it.
The image placeholder SVG I use on my site.

It recently occurred to me, though, that CSS is now powerful enough to handle this case without that <div>, using a combination of the :has() and :only-child pseudo-classes.

The figure:has(figcaption:only-child) selector selects <figure> elements that contain only a <figcaption>. Here’s how we can use this selector to add my image placeholder SVG:

figure:has(figcaption:only-child) {
    width: 100%;
    height: auto;
    aspect-ratio: 4 / 3;
    background-image: url(/images/placeholder.svg);
    background-repeat: no-repeat;
    background-position: center;
    background-size: contain;
}

It’d be nice if the <figcaption> appeared below the image, though, like it does when there’s a real image there. This is relatively easy to accomplish with a combination of relative positioning and tweaking some background properties:

figure:has(figcaption:only-child) {
    position: relative;
    background-position: top;
    background-size: 90% 90%;
}

figure:has(figcaption:only-child) figcaption {
    position: absolute;
    bottom: 0;
    left: 0;
    right: 0;
}

There! Now we automatically get placeholder images for <figure> elements that lack content.