htmlaccessibilityfigurefigcaption

How to specify a caption outside of figure element


I have a situation where I need physically separate a caption from the associated figure, e.g. this sort of markup:

<figure>
  <img src="…" alt="…"/>
</figure>

…later in the code…

<figcaption>
  Caption text
<figcaption>

This isn't semantically correct (there are existing SO questions addressing it), and it produces a validation error. Given that, I'm wondering what the appropriate way to represent this would be (considering markup and accessibility).

Ideally, there would be some sort of attribute (like the label elements for attribute) that would let you associate a figcaption with a figure. Currently I am using an aria-describedby attribute (see below), but I'm not sure if this is accurate either.

<figure aria-describedby="caption">
  <img src="…" alt="…"/>
</figure>

…later in the code…

<div id="caption">
  Caption text
<div>

Solution

  • There are a couple of ways you could approach this, either by adding a role and using aria-labelledby, or with JavaScript.

    Method 1 - HTML Only

    The aria-labelledby attribute is only recognized on form controls (e.g. buttons, inputs, checkboxes, etc.) or on elements that have a role attribute assigned. Without either of these conditions, the accessible name computation will not be calculated on the element.

    <figure role="region" aria-labelledby="caption-text">
        <img src="/images/logo.png" alt="your image alt text" />
    </figure>
    
    <p>…later in the code…</p>
    
    <div id="caption-text">Caption text</div>
    

    In my testing on NVDA/Chrome this works as long as the alt attribute on the image is not empty. I would strongly recommend testing this using different browsers and screen-readers before deploying to a production environment for the reason that it's essentially a label on a non-interactive element.

    Method 2 - JavaScript and CSS

    This method is more inline with your original question. It produces a non-visual figcaption element as a child of the figure element.

    <style>
        .sr-only {
            clip: rect(1px, 1px, 1px, 1px);
            clip-path: inset(50%);
            height: 1px;
            width: 1px;
            margin: -1px;
            overflow: hidden;
            padding: 0;
            position: absolute;
        }
    </style>
    
    <figure id="fig">
        <img src="/images/logo.png" alt="your image alt text" />
    </figure>
    
    <p>…later in the code…</p>
    
    <div id="caption-text" aria-hidden="true">Caption text</div>
    
    <script>
        document.addEventListener("DOMContentLoaded", () => {
            
            // create a figcaption element
            const caption = document.createElement("figcaption")
            
            // get innerText from div#caption-text and add to new figcaption element
            caption.innerText = document.getElementById("caption-text").innerText
            
            // assign a class to visually hide
            caption.className = "sr-only"
    
            // append figcaption to the figure element
            document.getElementById("fig").appendChild(caption)
        });
    </script>