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:
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.