Last Updated: February 25, 2016
·
1.165K
· nickheer

Create figures of arbitrary width with optional borders and captions

I was having a real doozy of a time creating figure code for my weblog which would satisfy the following requirements:

  1. Width must be defined in HTML, not CSS. I'd like to be able to use figures of varying widths.
  2. I'd like to be able to include a figcaption sometimes, but not require it. This caption should match the width of the figure (i.e. the image).
  3. I'd like to be able to put top- and bottom-borders on the entire figure sometimes, but not always. That means that the figcaption should be within these borders, and not duplicate the border.

This is in addition to the usual requirements of inline figures (ability to float left or right, or be centred; be fed images, audio, or video without code modification).

What I came up with is a little bit of a hack, but it works beautifully on my website. Let's start with the basic figure code:

figure{
    display: table;
    margin: 0;
}

figcaption{
    font-size: 11px;
    color: #90909f;
    display: table-caption;
}

Right off the bat, we're doing something a bit strange here: the figure is set to display as a table. Huh?

This came from a Stack Overflow thread which ensures that images of varying widths can be displayed; this is matched by the caption via the display property.

Problem number one: solved.

You'd expect I'd be able to solve problem number two and three in one swoop with some code like this:

figure.withborders{
    padding: 10px 0px 10px 0px;
       border-top: 1px solid #ccccdf;
    border-bottom: 1px solid #ccccdf;
}

That is, if I specify a withborders class, add some borders. Else, don't.

Turns out this has a giant issue with some browsers: if a figcaption is specified like this…

<figure class="withborders">
<img src="path/to/img.jpg" width="400">
<figcaption>Here's a caption.</figcaption>
</figure>

…the caption will be rendered outside of the bottom border. Weird, right?

That's where this code gets kinda janky. In order to compensate for this, we'll give the img the borders:

figure.withborders img{
    padding: 10px 0px 10px 0px;
       border-top: 1px solid #ccccdf;
    border-bottom: 1px solid #ccccdf;
}

"But wait," you begin, "if the image gets the borders, then the caption will still be rendered outside of the bottom border." You're damn right. But we can work around that, like this:

figure.withborders figcaption{
    padding-bottom: 10px;
    border-bottom: 1px solid #ccccdf;
}

figure.withborders figcaption+img{
    border-bottom: 0;
}

And then to the figcaption, we add caption-side: bottom;. Bingo.

The HTML looks like this:

<figure class="withborders">
<figcaption>Here's a caption.</figcaption>
<img src="path/to/img.jpg" width="400">
</figure>

Yeah, we're going to put the caption on top in the HTML, but it will render at the bottom (remember the caption-side property?).

So this solves issue number two and number three beautifully, for the most part. There is one small problem with this method: since all of this is specified in a CSS file, RSS readers and readability-type extensions (Instapaper, Safari Reader, etc.) will draw the caption on top. Oh well – at least it works on the website as intended.

Finally, a small bit of CSS to make figures with <audio> render nicer:

figure audio{
    width: 100%;
    height: 40px;
}

The height is specified to ensure everything draws nice on iOS.

Hope this helps someone out there. It certainly took a little bit of trial-and-error to get it to work just right, but I think it's a fairly elegant solution.