How to accessibly hide table caption element? - html

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>

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.

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:
.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);
}
.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);
}
.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><tr></code> in the <code><thead></code> which could be the reason why the last <code><tr></code> is being skipped.</p>

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.

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

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">

Related

Read more Links do not have descriptive text

I have a page which contains a list of items. Each item contains a 'Read more' link that points to a different page. But when I run the lighthouse tool on that page, it complains that links do not have a descriptive text. Now I cannot change the Read more text here.
Read more
Read more
Read more
Is there any other way to resolve this?
I had the same problem.
Attribute aria-label does not works, lighthouse still display issue.
I fixed it by adding hidden detailed text inside link.
Read more<span class="screen-reader-text">Details</span>
<style>
.screen-reader-text {
border: 0;
clip: rect(1px, 1px, 1px, 1px);
-webkit-clip-path: inset(50%);
clip-path: inset(50%);
height: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute !important;
width: 1px;
word-wrap: normal !important;
word-break: normal;
}
</style>
Note about .screen-reader-text: this CSS grabbed from wordpress default Twenty Seventeen theme.
Yes, you can use aria-label in order to provide more descriptive text to Assistive Tech such as a screen reader.
Read more
Assistive tech will read the contents of the aria-label out instead of "Read More".
Bear in mind that the text you enter should be enough to know where the link will take you and not context dependent (the link text should make sense on its own) if possible.

Is it ok to combine aria-label and aria-describedby?

I have searched around and I'm not finding any information if it is ok to combine the aria-label and aria-describedby for an element and if it would cause confusion to someone using a screen reader?
I have a list of many items and each item has a title and then next to the title is a PDF icon to download a pdf of the item, like this:
<ul>
<li>
<div id="item-{{item.id}}">{{item.title}}</div>
<button class="icon-pdf"></button>
</li>
</ul>
I am wondering if I can do something like this and if it would still make sense to the user and if screen readers would handle this scenario:
<ul>
<li>
<div id="item-{{item.id}}">{{item.title}}</div>
<button class="icon-pdf"
aria-label="Download PDF button"
aria-describedby="item-{{item.id}}">
</button>
</li>
</ul>
Perhaps it would be better to convert the button to a link and just use a title attribute like this?
<a href="javascript:void(0);//Download PDF"
class="icon-pdf"
title="Download PDF"
aria-describedby="item-{{item.id}}">
</a>
Short Answer
There is no need to add the extra information you are trying to add if you use a hyperlink and recommended practices of adding file type and size in brackets (oh and language if your site is multi-language).
Long Answer
To answer the original question, yes you can use aria-label and aria-describedby together. They serve different purposes.
aria-label is for providing a usable name for a control, it overrides any semantically derived information (i.e. button text).
aria-describedby is used to provide additional information for custom controls etc. It can also be used to provide hints to screen reader users. Also this answer I gave has information about support for aria-describedby etc. Something to consider.
If you use them together you would get the aria-label read first and then get the aria-describedby information read after.
Quick example of aria-label and aria-describedby together
<button aria-label="read first" aria-describedby="extra-info">Not Read Out</button>
<div class="visually-hidden" id="extra-info">This would be read second</div>
In the above example it would read "read first, This would be read second", notice the "Not Read Out" original button text is completely overwritten.
Your use case doesn't really need these though
With all of the above being said, here are a few suggestions for your use case as there is no real need for WAI-ARIA here:-
Even if a document is being downloaded on the same page you should use a hyperlink. The main reason for this is when JavaScript fails on your page (or for those who still browse the internet without JavaScript) there is a fallback so the document is accessible. Additionally this helps with SEO if you want the document to get indexed etc. (I know, I dare to mention SEO on Stack Overflow!). Finally it is semantically correct, it is a linked file and that is ultimately what hyperlinks are for.
If information is useful to screen reader users it is probably also useful to other people, i.e. those with cognitive impairments. However in this use case it would be better that the control that performs the action contains all the relevant information.
Generally (if your design can be adjusted to allow for it) it is a good idea to include the file type and file size as additional information in brackets next to any download.
Don't use the title attribute, it is not a very accessible attribute and is useless to most screen reader users as it will not be announced. (It is also useless to any keyboard only users etc.)
WAI-ARIA is useful for supplemental information, the general rule is a control should work without it and WAI-ARIA is for progressive enhancement.
Putting it all together
You will notice in the following example I have completely removed the need for the "Download PDF" extra information.
Because a hyperlink is semantically correct and the fact we state it is a PDF in brackets (plus file size) there is no need to tell people that this will download the PDF, they already know this!
I have done two different examples for you, one with the file type and size visible, one with them visible only to screen reader users.
I have added comments to the first example to explain bits. Any questions just ask!
body {
font-family: Century Gothic;
background: #272727;
}
.btn {
float: left;
width: 25%;
height: 30px;
padding: 1px 0px;
min-width: 200px;
margin: 2% .8%;
overflow: hidden;
background: #527EBF;
}
.btn:hover {
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.4);
border-radius: 5px;
background: #666;
}
.btn a {
text-decoration: none;
}
.btn img {
width: 22px;
margin: 0 5px;
transition: all .5s ease;
position: relative;
left: 0;
transform: scale(0.7);
}
.btn .container span.text {
font-size: 12px;
color: #fff;
position: relative;
left: -3px;
top: -8px;
transition: all .45s ease-in-out;
}
.visually-hidden {
border: 0;
padding: 0;
margin: 0;
position: absolute !important;
height: 1px;
width: 1px;
overflow: hidden;
clip: rect(1px 1px 1px 1px); /* IE6, IE7 - a 0 height clip, off to the bottom right of the visible 1px box */
clip: rect(1px, 1px, 1px, 1px); /*maybe deprecated but we need to support legacy browsers */
clip-path: inset(50%); /*modern browsers, clip-path works inwards from each corner*/
white-space: nowrap; /* added line to stop words getting smushed together (as they go onto seperate lines and some screen readers do not understand line feeds as a space */
}
<div class="btn">
<a href="link-to-pdf.pdf"> <!--obviously if you want to intercept this with an event listener in JS then do so but leave the URL for fallback-->
<div class="container">
<img src="https://upload.wikimedia.org/wikipedia/commons/thumb/8/87/PDF_file_icon.svg/267px-PDF_file_icon.svg.png" aria-hidden="true"/> <!-- hide the icon from screen readers with `aria-hidden`, preferably use a **inline** SVG instead of external image to save an uneeded request. -->
<span class="text">Item Name (PDF, 21MB)</span> <!-- added the file type and size as this is useful information for people, made it visible to all. If yourdesign won't allow for this then hide it as per second example -->
</div>
</a>
</div>
<div class="btn">
<a href="link-to-pdf.pdf">
<div class="container">
<img src="https://upload.wikimedia.org/wikipedia/commons/thumb/8/87/PDF_file_icon.svg/267px-PDF_file_icon.svg.png" aria-hidden="true"/>
<span class="text">Item Name Hidden file size info <span class="visually-hidden">(PDF, 21MB)</span></span>
</div>
</a>
</div>

How to make an HTML link displayed as an icon accessible?

I have some links that are displayed only as icons (don't have any text):
.icon-link {
background-image: url(...);
}
How do I make this link accessible for people not accessing the website visually (using screen readers)?
I see few approaches possible, but cannot find any resources on which one is actually right, or best supported:
Adding aria-label attribute on <a>
Adding title attribute on <a>
Adding text inside <a> and then hiding it visually with CSS
Short Answer
Use visually hidden text.
Longer answer
Adding a title offers very little in the way of accessibility. Here is an interesting article on the subject, it links out to further information.
So with that in mind that leaves option 1 and 3 as viable options, however the best compatibility is using visually hidden text.
You see support for aria-label is surprisingly low (scroll down the page to the aria-label section), whereas visually hidden text using the example below will cover browsers all the way back to IE6!
I answered about the most robust way to do visually hidden text (with explanations of why I do each item) in this stack overflow answer. I have copied the same below just for your reference.
For your use case just add a span within the link with the visually-hidden class.
.visually-hidden {
border: 0;
padding: 0;
margin: 0;
position: absolute !important;
height: 1px;
width: 1px;
overflow: hidden;
clip: rect(1px 1px 1px 1px); /* IE6, IE7 - a 0 height clip, off to the bottom right of the visible 1px box */
clip: rect(1px, 1px, 1px, 1px); /*maybe deprecated but we need to support legacy browsers */
clip-path: inset(50%); /*modern browsers, clip-path works inwards from each corner*/
white-space: nowrap; /* added line to stop words getting smushed together (as they go onto seperate lines and some screen readers do not understand line feeds as a space */
}
<p>visible text <span class="visually-hidden">hidden text</span></p>
Added Bonus of visually hidden text
As #QuentinC pointed out in the comments there is another great reason to use visually hidden text over the other methods.
If a user uses a browser that does not support CSS (there are still a few text only browsers that people use) then the text will be displayed.
Always reconsider using visually hidden text. Not because it is bad, but because it leads to false belief that the solution is accessible for everyone when it's only accessible to a small subset of the population.
Using hidden text won't help people not using screenreaders to know the action performed by the link when meaning of the image might be difficult. Screenreader users are a small part of the population targetted by accessibility rules.
Regarding the title attribute, it won't hurt anyone to improve accessibility if you inform standard mouse users of the action performed by the link. It will help them. If a title attribute is not always recommended, you might opt for any solution that would show the text when the element is focused with the mouse or with the keyboard.
You also must remember that not showing text will not help people using voice navigation or eye tracking device.
When using the title attribute, you must always consider using it conjointly with the aria-label attribute, and not replacing one with the other.
EDIT: simple example
.icon-link {
background-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' version='1.1' height='50px' width='120px'><text x='0' y='20' font-size='20'>🍕</text></svg>");
content: '';
background-repeat: no-repeat;
width: 20px;
height: 30px;
display: inline-block;
}
#pizza {
position: absolute;
display:none;
background:white;
color: black;
margin-left: 20px;
}
a:focus #pizza, a:hover #pizza {
display: block;
}
<div id="pizza">Pizza!</div>

How to hide a text and make it accessible by screen reader?

I have a simple text that gets updated on an action and I want that to be announced by the screen reader. But I don't want that text to be visible on the web page. I tried display: none and visibility: hidden, but seems like they are not accessible by the screen reader softwares. I found a way to make this work - that is by absolute positioning the element all the way with negative 999999 value which will make it off screen and hidden from the webpage. I am not really a fan of this solution. Is there a more elegant way to achieve this?
<span class="aria-invisible" aria-live="polite">5 selections have been made.</span>
.aria-invisible {
display: none; //either of these two attributes
visibility: hidden;
}
A better solution to the bootstrap "sr-only" class.
There are numerous problems with the Bootstrap "sr-only" class.
First of all you will see from this discussion that a negative margin can cause issues on VoiceOver.
Secondly you must account for words wrapping one per line as screen readers do not read line breaks
Finally clip has been deprecated.
To fix point 1 simply don't add a negative margin.
To fix point 2 add white-space: no-wrap to ensure words do not end up 'one per line' and cause words to get smushed together.
To fix point 3 we add clip-path: inset(50%) as this clips to a 0px square, we keep clip as at the moment this has great coverage, clip-path is used to future-proof your solution.
Please find below a much more robust class, as of yet I have not found a screen reader / browser combo that does not work as expected with this.
I have this class on a few different forums being tested, so far so good but if someone can find a problem with it please let me know as I will be submitting it everywhere.
.visually-hidden {
border: 0;
padding: 0;
margin: 0;
position: absolute !important;
height: 1px;
width: 1px;
overflow: hidden;
clip: rect(1px 1px 1px 1px); /* IE6, IE7 - a 0 height clip, off to the bottom right of the visible 1px box */
clip: rect(1px, 1px, 1px, 1px); /*maybe deprecated but we need to support legacy browsers */
clip-path: inset(50%); /*modern browsers, clip-path works inwards from each corner*/
white-space: nowrap; /* added line to stop words getting smushed together (as they go onto seperate lines and some screen readers do not understand line feeds as a space */
}
<p>visible text <span class="visually-hidden">hidden text</span></p>
I did encounter this problem in the past.
Bootstrap has this sweet class sr-only that actually hides the content on the webpage but is accessible by the screen readers. You can check this link
Moreover, if you are not using bootstrap, you can simply implement the class yourself in your code.
.aria-invisible {
border: 0;
clip: rect(0 0 0 0);
height: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
width: 1px;
}
<span class="aria-invisible">5 selections have been made. </span>
I hope this helps.
Using aria-label attributes is the way to do (example below)
Is there a more elegant way to achieve this?
Do not hide the element. Yes. I am not answering your question, but I am addressing the problem.
Screenreaders are only a subpart of assistive technologies used by a small part of people targeted by accessibility guidelines.
Imagine using a screen magnifier for instance where you do not have a global view on the full screen. Imagine having some cognitive disabilities which makes difficult for you to count or remember elements.
If you do consider that an information is important for blind people, then it's surely is for them AND for other people.
Now, instead of it being a long text, it can be a small counter with appropriate aria labelling:
<div role="status" aria-live="polite" aria-label="5 selections have been made.">
5 selections
</div>
I had the same problem with the text being out of position with the visually hidden class mentioned above. Some small changes to the class fixed this issue for me
.visually-hidden {
border: 0;
clip: rect(0 0 0 0);
clip-path: inset(50%);
height: auto;
margin: 0;
overflow: hidden;
padding: 0;
position: absolute;
width: 1px;
white-space: nowrap;
}

A better alternative than for showing empty HTML table cells?

It's a classic problem - when you have an empty table cell the browser doesn't render borders around it. There are also two well-known workarounds. One is to place an in the table cell; the other is to use the empty-cells:show CSS property.
Unfortunately both have drawbacks. is kind of ugly when it comes to selecting text and copy-pasting it. You get a lot of spaces where there shouldn't be any, perhaps even with an exotic Unicode character. empty-cells:show should address exactly this problem, but unfortunately it only works properly in IE starting with version 8 (and then only in standards-compliant mode). It can be made to work in other versions by also specifying border-collapse: collapse, but sometimes this is what is NOT desired. In my case I have a fairly complex table and it relies on border-collapse:separate and would otherwise create quite a messy CSS/HTML soup.
So what are other things that you might put in a table cell that would make IE draw the borders yet not be visible or copyable? For all other browsers the empty-cells:show already does the trick, so I really just need to fool IE.
You can also put invisible br element:
<td><br style="visibility:hidden"></td>
It is ridiculous amount of unnecessary code, but it makes the trick - no additional text added yet cell is displayed.
Note that <br/> is invalid HTML syntax according to the official specifications http://www.w3.org/TR/html401/struct/text.html#edef-BR. It is valid XHTML syntax however.
You can show the cells with this CSS code. I successfully tested it in Safari and Firefox. I guess it works in other browsers as well.
table {
width: 100%;
border: 0;
empty-cells: show;
}
td {
border: 1px solid grey;
}
td:empty:after {
content: '.';
color: transparent;
visibility: hidden;
}
/* alternate background */
tr:nth-child(odd) td {
background: rgba(0, 0, 0, 0.2);
}
tr:nth-child(even) td {
background: rgba(0, 0, 0, 0.1);
}
<table>
<tr>
<td>Row</td>
<td>1</td>
</tr>
<tr>
<td></td>
<td></td>
</tr>
<tr>
<td>Row</td>
<td>3</td>
</tr>
</table>