22

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>
Scott Buchanan
  • 1,163
  • 1
  • 11
  • 28

5 Answers5

11

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.

2

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>
zer00ne
  • 41,936
  • 6
  • 41
  • 68
  • Thanks. The omission of the `` in the original was just an accidental in putting together the minimal example. The original case this was derived from had the `` as it should. – Scott Buchanan Feb 27 '19 at 02:26
  • As to the styles, the rule which actually is key in causing the issue is the `position: static`. The rest really could be eliminated for the same effect on VoiceOver. For reference, the block of styles I showed are the standard rules from the Magento framework for hiding visible text to still be available to screen readers. I'll test out the examples later, but a requirement is that the caption not affect the visible layout at all, so just making text transparent isn't workable. – Scott Buchanan Feb 27 '19 at 02:38
  • *"...but a requirement is that the caption not affect the visible layout at all, so just making text transparent isn't workable."* The text is the least modified out of all of the methods (your code included). It is the same as `color: red`. Click above the second table and select -- you should see the highlighted text. – zer00ne Feb 27 '19 at 06:52
  • In both your second and third examples there is space reserved for the caption even though it is not visible. That's what I mean by affecting the visible layout. When I ask if there's any way to "accessibly hide" it, I mean hiding it in a way that has no impact visually at all, including in spacing, yet still leaving it available to screen readers. – Scott Buchanan Feb 27 '19 at 15:46
  • For the record, I did just test with VoiceOver. Your suggestion #1 ("clipped") still has the same issue with VoiceOver as I mentioned originally. Your suggestions #2 ("transparent") and #3 ("collapsed") do avoid the VoiceOver issue, but they don't meet the qualification of being "visually hidden" as I mentioned in my last comment. – Scott Buchanan Feb 27 '19 at 15:49
  • Can you post a link to an article that explains what the qualifications "visually hidden" and what "accessibly hide" means? Your use of such terms is ambiguous. BTW did you bother to adjust the height of #2 and #3 down to 1px? I believe I mentioned something to that effect? – zer00ne Feb 27 '19 at 16:02
  • You did mention forcing the height to 1px. But that's still 1px in the layout. That's enough to throw off alignment. You're right there is some ambiguity in the terms I'm using, but I'm using them as they're used in the general web accessibility community. For example: https://webaim.org/techniques/css/invisiblecontent/ or https://www.w3.org/WAI/tutorials/forms/labels/#note-on-hiding-elements – Scott Buchanan Feb 27 '19 at 19:55
1

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.

Hrvoje Golcic
  • 3,446
  • 1
  • 29
  • 33
  • 2
    I appreciate the spirit of this idea, though in practice a sighted user may be able to perceive the purpose of a table via a glance at the whole structure (nonlinearly), whereas the caption is more necessary for the screen reader user, who must interact with the content in a linear way. The original example that spurred this was a table of items in an ecommerce cart. – Scott Buchanan Feb 04 '22 at 13:08
  • Of course, every situation is different, and on top of all, the most important is not to assume anything but do the testing with the real users, many of them. Generally avoiding user-group-specific solutions is always a good advice especially if you make your website inaccessible to some of the users while trying to make it accessible for others. So I needed to highlight this. The main point here is, as mentioned, we should never think of "sighted" and "blind users", for the reasons described, screen readers and such solutions are not used only by blind people. – Hrvoje Golcic Feb 05 '22 at 12:40
0

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);
}
0

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">
Bryan Sullivan
  • 318
  • 2
  • 7
  • 2
    If you're using HTML 4, then the `summary` attribute is a possibility, but in HTML 5, that attribute is deprecated. See https://html.spec.whatwg.org/multipage/obsolete.html#attr-table-summary – slugolicious Dec 14 '21 at 18:29
  • Good catch. Although my suggestion still works at the moment, it should be avoided. – Bryan Sullivan Mar 04 '22 at 23:07