skip to Main Content

Is there any way to accessibly hide a table caption without breaking how screen readers interpret the rest of the table? Hiding a <caption> with typically recommended styles for hiding an element visually breaks the behavior of VoiceOver, causing it to skip the last row in the table when reading through linearly using the “next” keystroke. (It is possible to force VoiceOver into the last row by explicitly navigating down a column, but that requires the user to know to do this.)

I recognize this may be a bug in VoiceOver itself, but if there’s a clean workaround, that would be ideal since WCAG requires accessibility with actually available assistive technology.

Here’s a minimalist example to demonstrate:

Update: The style rules below are the standard rules used in the Magento framework to visually hide elements while leaving them accessible to screen readers. The key rule causing the VoiceOver behavior is the position: absolute; however, if this is simply removed, the layout flow is impacted.

caption {
    border: 0;
    clip: rect(0, 0, 0, 0);
    height: 1px;
    margin: -1px;
    overflow: hidden;
    padding: 0;
    position: absolute;
    width: 1px;
}
<table>
  <caption>Table of Fruits</caption>
  <thead>
    <tr>
      <th>Fruit</th>
      <th>Color</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Apple</td>
      <td>Red</td>
    </tr>
    <tr>
      <td>Pear</td>
      <td>Green</td>
    </tr>
  </tbody>
</table>

<p>Voiceover will jump straight from "Red" in prior table to this paragraph, skipping the last row.</p>

5

Answers


  1. A Few Discrepancies

    <th> Needs <tr> as a Parent to be Valid

    The OP (Original Post) code didn’t have a <tr> in the <thead> which could be the reason why the last <tr> is being skipped. Invalid HTML has a tendency to confuse applications such as VoiceOver.


    Three Methods

    Disclaimer: Not Tested – Caveat Emptor

    The following demo has three <table>s with identical HTML markup, CSS rules, and text content. Each <caption> has a different .class that employ a specific method of hiding content:

    1. .clipped – Assuming that clipping content needs a length: clip: rect(0, 0, 0, 0); looks dubious. Some other properties and values looked to be ad-hoc as well so try replacing caption {...} rule set with:

      .clipped {
        position: absolute !important;
        height: 1px; 
        width: 1px; 
        overflow: hidden;
        clip: rect(1px, 1px, 1px, 1px);
      }  
      
    2. .transparent – This is simply assigning a transparent color to text. Height is still there (which VoiceOver requires), but it can be adjusted if needed. opacity: 0 is also an option, but there are certain situations in which opacity: 0 is considered the same as visibility: hidden (which VoiceOver ignores).

      .transparent {
        color: rgba(0, 0, 0, 0);
      }  
      
    3. .collapsed – This collapses an element’s content but retains its height so VoiceOver might recognize it.

      .collapsed {
        visibility: collapse;
      }
      

    Demo

    table {
      border: 1px solid #000;
      table-layout: fixed;
      border-collapse: collapse;
      min-width: 200px;
    }
    
    th,
    td {
      width: 50%;
      border: 1px solid #000;
    }
    
    .clipped {
      position: absolute !important;
      height: 1px; 
      width: 1px; 
      overflow: hidden;
      clip: rect(1px, 1px, 1px, 1px);
    }
    
    .transparent {
      color: rgba(0, 0, 0, 0);
    }
    
    .collapsed {
      visibility: collapse;
    }
    <table>
      <caption class='clipped'>CAPTION</caption>
      <thead><tr><th>TH</th><th>TH</th></tr></thead>
      <tbody><tr><td>TD</td><td>TD</td></tr>
      <tr><td>TD</td><td>TD</td></tr></tbody>
    </table>
    
    <table>
      <caption class='transparent'>CAPTION</caption>
      <thead><tr><th>TH</th><th>TH</th></tr></thead>
      <tbody><tr><td>TD</td><td>TD</td></tr>
      <tr><td>TD</td><td>TD</td></tr></tbody>
    </table>
    
    <table>
      <caption class='collapsed'>CAPTION</caption>
      <thead><tr><th>TH</th><th>TH</th></tr></thead>
      <tbody><tr><td>TD</td><td>TD</td></tr>
      <tr><td>TD</td><td>TD</td></tr></tbody>
    </table>
    
    <p>The <abbr title="Original Post"><b>OP</b></abbr> code didn't have a <code>&lt;tr&gt;</code> in the <code>&lt;thead&gt;</code> which could be the reason why the last <code>&lt;tr&gt;</code> is being skipped.</p>
    Login or Signup to reply.
  2. Well… I see that you are using a caption tag just for accessibility, which means that you don’t want to show it visually; I suggest simply not using it and instead use aria-label in your table tag, which will make it accessible for screen readers.

    <table aria-label="Table of fruits"> ... </table>

    Read the first paragraph of this page to get an idea about aria-label usage.

    Login or Signup to reply.
  3. Since position: absolute; is what causes the problem, a pragmatic solution is to skip it and use margin-top: -1px; instead. Tested and verified i Chrome + voiceover.

    .clipped {
      position: absolute !important;
      height: 1px; 
      width: 1px; 
      overflow: hidden;
      clip: rect(1px, 1px, 1px, 1px);
    }
    
    Login or Signup to reply.
  4. I’m a little late to this discussion, but there is a solution that hasn’t been mentioned yet.

    You can use the summary attribute on the table element. The summary attribute will not affect your layout, but will be read by the screen reader.

    If you use the caption element or aria-label attribute, they will override the summary attribute. So just use summary by itself.

    <table summary="Table of Fruits">
    
    Login or Signup to reply.
  5. You do not want to hide <caption> visually.

    A bit late to the party but I feel an urge to highlight the importance of avoiding to treat disabled users differently. That simply means to prefer solutions that generally work for all users out-of-the-box. Try not reinvent the wheel or over-complicating with screen-reader-only solutions, just leave it as it is and provide the same content to all users. In this specific case I’d either make caption visible for all, or for no one. Why not to show caption to all users? If the table content is so complex that it needs to be described to a screen reader then you might ask yourself whether it’s time to optimize the actual content for all users, or describing it to all users, not only screen readers. Because when you make something accessible for a screen-reader-only then likely you made it inaccessible for many other users. Hence you didn’t make it accessible.

    The worst here is the assumption that only screen-reader users will require some solution. But there are no screen-reader users. Such assumptions about the users should never be made. Accessibility is not screen-readers. There are so many other disabilities, use-cases and assistive technology. Many screen reader users want to share the content and if they "see" (or hear) something that their friend cannot access, it will look weird. Bear in mind also that many screen reader users are not blind. They might be using zooming and will also find confusing the fact that screen reader is reading the content which appears not to exist on the page.

    Yes, there are always some exceptions, such would be "skip links" and similar, but all such practices are common and something users are familiar to. Those are usually well thought through for all user groups. Hence "skip links" would become visible when focused etc.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search