Skip to main content

See No Evil: Hidden Content and Accessibility

By Paul Hebert

Published on February 25th, 2019

Illustration of an eye bracketed by less than and greater than signs

When I first started learning web development I thought hiding content was simple: slap display: none; onto your hidden element and call it a day. Since then I’ve learned about screen readers, ARIA attributes, the HTML5 hidden attribute, and more!

It’s important to ensure our websites are accessible to everyone, regardless of whether or not they use a screen reader, but with this myriad of options, how do we know when to use what?

There are four main scenarios where you may wish to hide content:
1. Hiding content for everyone, regardless of whether they use a screen reader
2. Hiding content for screen readers while showing it to other users
3. Showing additional content for screen readers while hiding it from other users
4. Hiding content at specific screen sizes

Let’s dive deeper into each of those scenarios to learn how to handle them.

When hiding content for all users we can take advantage of HTML5’s hidden attribute. The hidden attribute signals that content should not be rendered, regardless of medium or screen reader use. In supported browsers it also hides the content from view, similar to display: none;.

It may feel odd to be handling display in your HTML instead of your CSS, but there’s a good reason for it! All devices should respect the hidden attribute, including browsers, screen readers, and printers, even if they don’t load your stylesheets.

This technique is most often used when a site is dynamically showing and hiding content, like a popup or accordion. You may need to combine the hidden attribute with a CSS class to allow for transitions. In that case, just make sure you update the hidden attribute whenever you change visibility by another means.

There’s one extra wrinkle when using hidden. It’s not supported in Internet Explorer 10 and below so if you do use hidden you should also set display: none; !importantFootnote 1 in CSS to ensure the content is hidden in all browsers.

<div class="example" hidden></div>

<style>
  .example[hidden]{
    display:none !important;
  }
</style>

This can also be set as a global style using attribute selectors.Footnote 2

[hidden] {
  display: none !important;
}

Some content is not important for understanding a web page, but is added to make the design more visually appealing. For example, icons and glyphs can provide a nice visual polish, but tend to be unhelpful — and sometimes downright distracting — for screen reader users. In this scenario we’ll want to hide the content from screen readers while showing it to everyone else.

In this case we’ll use the aria-hidden attribute. aria-hidden is a boolean attribute so it can be set to true or false. Setting the attribute to false is the same as not including it at all, so you’ll generally want to set it to true and use it like this:

<div class="my-glyph" aria-hidden="true"></div>

aria-hidden="true" should not be confused with role="presentation" which strips the semantic meaning of an element from the accessibility tree. Here’s a helpful article outlining the difference between the two.

A good web page design often uses visual clues to convey information to the viewer. It’s important to structure your page so that screen reader users get these same clues from your text. For example, pagination may be obvious when laid out visually, but might read as a meaningless list of numbers over a screen reader. In these scenarios it’s helpful to include extra information for screen readers without cluttering up your visual design.

Setting display: none; hides the content but also removes it from the accessibility tree so screen readers won’t read it. Because of that it’s best to fall back to other CSS tools to hide the content while keeping it in the accessibility tree.

The 18f site has a great solution to hide content visually while keeping it in the accessibility tree for screen readers:

.sr-only {
  border: 0; 
  clip: rect(0 0 0 0); 
  height: 1px; 
  margin: -1px;
  overflow: hidden;
  padding: 0;
  position: absolute;
  width: 1px;
}

In the comments of this post Edward Martin pointed out that the CSS clip property is deprecated and that we should be using clip-path. clip-path isn’t fully supported yet, so for now it’s best to include both clip and clip-path.

In addition, Kimblim pointed out that this technique can cause screen readers to skip the spaces in between words and suggested adding white-space: nowrap; to avoid this.

Following this advice leaves us with this more robust class:

.sr-only {
  border: 0; 
  clip: rect(0 0 0 0); 
  clip-path: polygon(0px 0px, 0px 0px, 0px 0px);
  -webkit-clip-path: polygon(0px 0px, 0px 0px, 0px 0px);
  height: 1px; 
  margin: -1px;
  overflow: hidden;
  padding: 0;
  position: absolute;
  width: 1px;
  white-space: nowrap;
}

The aria-label attribute can also be used to provide additional information to screen readers though generally the CSS solution is preferable. It’s worth learning both techniques and knowing when to use one or the other.

When building responsive web pages designers often choose to display content at certain screen sizes but not others using media queries. In these scenarios the content should generally be included in the accessibility tree for screen readers, so hidden and aria-hidden are not necessary.Footnote 3

Now we’ve got a lot of tools in our toolbox. We can hide content for all users, for screen readers, for users not using screen readers, and for specific screen sizes. Learning how to properly hide content in an accessible way is a valuable skill for anyone touching the front-end, and your users will appreciate it!

Footnotes

  1. Šime Vidas pointed out on twitter that using !important for this declaration better matches the behavior of supported browsers.  Return to the text before footnote 1
  2. A previous version of this post incorrectly claimed that IE 7 and 8 did not support attribute selectors. Aaron Gustafson kindly pointed out that this was incorrect, and that both of those browsers support attribute selectors.  Return to the text before footnote 2
  3. Patrick H. Lauke pointed out that one of the paragraphs in the original post was somewhat misleading. That paragraph has been removed in the current version of this post. You can see more discussion in the post comments.  Return to the text before footnote 3

Comments

Patrick H. Lauke said:

If for some reason your design requires including the same content once for small screens and again for large screens, then one of the duplicates should get the aria-hidden="true" attribute.

Or, simply make sure that the visually hidden duplicate is set to display:none;, which - as noted in the section just before that - removes the content from assistive technologies / screen readers already anyway.

aria-hidden="true" is really mostly useful if you have to hide things in some other way (e.g. if you're not using display:none; because you're animating things fading in and out / moving in from the side and back again, etc. Then you want to make sure you dynamically juggle aria-hidden correctly. Another case where it's useful, of course, is if you want to explicitly hide something from assistive technologies that is otherwise visible / displayed (generally done because you've already provided a more appropriate alternative to AT anyway through other means, and having the visible part be announced by AT is redundant or leads to odd output - e.g. icon font containers set to aria-hidden="true" to avoid AT trying to read out the glyphs, and because there's already an adjacent visually hidden but accessible alternative / an aria-label or similar on the relevant control that uses the icon / etc.)

Replies to Patrick H. Lauke

Paul Hebert (Article Author ) replied:

Good point. Thanks Patrick! I appreciate the feedback!

You're right that using display: none; is generally the right approach here.

I thought about re-working that paragraph to go into more detail about when to use display: none; vs. aria-hidden="true" but it ended up being kind of confusing and I didn't feel like it was adding a lot of value so I ended up removing that paragraph from the post. I think this should be more accurate, and easier to understand.

Thanks for the correction!

kimblim said:

It would be helpful to add "white-space: nowrap;" to your sr-only class, as explained in this article: https://medium.com/@jessebeach/beware-smushed-off-screen-accessible-text-5952a4c2cbfe

Edwin Martin said:

According to the MDN web docs, using clip found in the sr-only class is no longer recommended.

Maybe we should find a new way to show content to screen readers only?

And isn't using aria-label better anyway?

https://developer.mozilla.org/en-US/docs/Web/CSS/clip

Replies to Edwin Martin

Paul Hebert (Article Author ) replied:

Hey Edwin!

Thanks for pointing out that clip is deprecated! It looks like the best option for now is including both clip and clip-path, but I'm open to other ideas as well.

aria-label is certainly useful but fills a slightly different use case and my research points to the CSS solution generally working better. It can be a tricky call when to use one or the other though.

I've added some more info to the article about both the clip/clip-path support and aria-labels.

Thanks!

Remi Lacorne said:

I would add that it is important to decode html entities from aria-labels, as some screen readers, like VoiceOver, will read   as a word, and the fact that a space is breakable or not is of no importance to screen reader, so it's always a safe net to remove them entirely. This is where aria-labels become useful, as you may need to keep the non-breakable spaces visually, but hide them in the audio version.

It is something that can happen a lot when receiving content from massive CMS, with many content editors.

A good library for this problem is 'he'.
https://github.com/mathiasbynens/he

Rob said:

For anyone wanting to a "screenreader-only" class, I would suggest to consider AMP's tried and tested version .i-amphtml-screen-reader. See the detailed notes in https://github.com/ampproject/amphtml/blob/master/css/amp.css