Adding captions to images?

This comment made me check my images source code and indeed there are broken paragraph tags! I use <figure> to present a caption with <figcaption>, but my images are in markdown format ![image title](img-link "image caption"). So, which is the correct way to add captions in Hugo, without having to convert hundreds of images to shortcodes?

In your render-image.html use .Title in the figcaption tag.

I did that and someone still complained it would break paragraphs, how ?

<figure>
   <figcaption><p>{{ with .Title }} {{ . | markdownify }} {{ end }}
   </p></figcaption>
 <a href="{{ .Destination | safeURL }}" target="_blank">
<img src="{{ replace .Destination ".jpg" ".jpg.small" | safeURL }}"  alt="{{ .Text }}"
{{ with .Title }}title="{{ . }}"{{ end }}/>
</a>
</figure>

The problem arises when you use an image inline, i.e. somewhere in the middle of your text. If the image forms its own paragraph (i.e. with an empty line before and after it), your figure approach should be fine.

I am using a modified version of this code (using <picture> tag wrapped in <figure> tag). So it already uses the .Title as caption. (I removed the figure and placed the caption as a span, but I had to make the image a block element with CSS display: block),

It’s not clear what your problem is with the figcaption.

And instead of block you could use inline-block on the container of the image and caption.

Please revisit the comment I linked by JOE.

Oh, I see… well I can’t help with the HTML spec. Each paragraph or new line in markdown is rendered as a <p></p> so no matter what you do in markdown syntax, that’s going to happen. If you want valid HTML then you need to use a shortcode to render the figure. A solid search/replace using regex could probably make changing all your content pretty painless.

Alternately, and I don’t know if this would work… is when you place the rendered content in your layouts {{ .Content }}, you could do a search and replace there to strip any immediate paragraph tags before and after a figure tag. That’s just a work around though, not best practice.

But you could also wrap the img and caption p in an inline-block div. Then it is still inline with the rest of the document. And the image and caption are aligned. Another workaround

I made this a while ago while learning about the similar thing:

This is false. Try it.

In both cases the image (or whatever the image render hook produces) is wrapped within a p element.

This would still produce invalid HTML. You cannot place a div element within a p element.

To produce semantically accurate and accessible HTML, use the embedded figure shortcode or create your own.

Again, with hundreds of images, I want to avoid this and stick with markdown syntax as closely as possible. So, for now, I will use the span element I implemented and some CSS to align everything as required.

I obviously have an opinion about the right way to do this given the availability of the figure element in HTML5, but using span elements in conjunction with the aria-labelledby attribute will produce accessible, valid HTML.

So I think you are on the right track.

No doubt. But I want to future-proof the content in case my team makes a decision to switch to another SSG (not happening, but just in case. I co-author one site with a team). I don’t want to be left with hundreds of shortcodes I will have a nightmare to fix.

Nice idea! I will look into it further.

What exactly is “false”? In the 2nd case, the image is not “wrapped” inside a paragraph, it is simply part of that paragraph as an inline element. In the first case, it is indeed wrapped in the paragraph, i.e. the p element is added kind of out of nowhere. Or rather: the markdown processor adds it because there’s a blank line after the image.

I beg to differ. Wrapping a div around an img and a p, as @gaetawoo suggested, should be fine. They did not suggest putting the div inside the p.

<div>
  <img...>
  <p class="caption">
</div>

That was actually the suggested approach before figure came along.

I think I am not communicating very well.

In both cases the image (or whatever the image render hook produces) is within a p element, and you cannot place a figure or div within a p.

I understand. However, I have a image render hook producing a figure, and this figure is not wrapped in a p, although the markdown image code is on its own line. Seems a bit weird, after having read this thread and the old issue on Github.

I’d love to see a test repo that demonstrates this.

1 Like

Since .Ordinal is not available in the image render hook, you’ll need to create the unique element id another way.

Crude image render hook example
{{- $id := now.UnixNano | string | add "caption-" }}
{{- with $caption := .Title | .Page.RenderString }}
  <span class="img-with-caption">
    <img src="{{ $.Destination }}" alt="{{ $.PlainText }}" aria-labelledby="{{ $id }}">
    <span class="img-caption" id="{{ $id }}">{{ $caption }}</span>
  </span>
{{- else }}
  <img src="{{ .Destination }}" alt="{{ .PlainText }}">
{{- end }}
{{- /**/ -}}

And the related CSS
span.img-with-caption {
  display: inline-block;
}
span.img-caption {
  float: left;
  text-align: center;
  width: 100%;
}
1 Like

Since I am generating different image sizes, can the ID be added to the <picture> tag instead?