Browsers package up an alternative version of a webpage, called an accessibility tree, for assistive technologies such as screenreaders or voice recognition software to consume. While accessibility trees are mostly drawn from the structure and semantics of the DOM, browsers can also factor in the page’s styles when assembling the tree. One instance of this is in common CSS approaches for hiding an element, such as:
display: none
visibility: hidden
height: 0
andwidth: 0
Browsers assume these elements are meant to be hidden for everyone, especially since toggling a style like display: none
tends to be a more performant way to remove and reveal content than deleting the element and rebuilding it. As a result, browsers take these styles as indications to remove that content from the accessibility tree. In solely HTML land, the hidden
attribute does the same thing.
But what if you want to hide content visually, but still expose it to assistive technologies? Visually-hidden styles (sometimes referred to as screenreader-only styles) are CSS rules which make an element invisible, similar to display: none
, without removing it from the accessibility tree.
The version of .visually-hidden
I’m currently using looks like:
.visually-hidden:not(:focus):not(:active) {
border: 0;
clip: rect(0 0 0 0);
clip-path: inset(50%);
height: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
white-space: nowrap;
width: 1px;
}
These rules hide the element by making it only a single pixel big and then clipping away that overflow. Because the element is still in the document order and, if applicable, could still be focused, this utility does not apply the changes if the element is currently focused or being followed as a link. For a more thorough breakdown, see The anatomy of visually-hidden by James Edwards on the TPGi blog.
This utility class has gone by other names through the ages. .sr-only
(short for “screenreader-only”) is particularly common, especially thanks to its use in libraries such as Bootstrap (now using “.visually-hidden
” as of Bootstrap v5) or Tailwind. Additionally, WordPress calls it .screen-reader-text
and Drupal used to call it .element-invisible
.
Uses for Visually-Hidden Styles
The following are a few common uses for visually-hidden styles. In some of these cases, using visually-hidden styles can be a hack for which it might be nice to have a more robust approach, but seeing as those approaches don’t necessarily exist yet, visually-hidden styles are the practical reality we have today.
Populating Icon Buttons’ Accessible Names
Interactive controls such as buttons need accessible names, which will get announced by screenreaders when the user navigates to them, and which voice control users can use to target the controls with their commands. Usually, controls like links and buttons get their accessible names from their text contents. However, icon-only buttons don’t have text contents, meaning they lack an accessible name.
One way to remedy this is with an aria-label
attribute, as in this sample markup for an “x” close button:
<button aria-label="Close">
<svg aria-hidden="true"> <!-- “x” icon --> </svg>
</button>
This works perfectly fine for most sites! However, it introduces a tradeoff that might be an issue for some sites: aria-label
does not reliably auto-translate and it might get missed in manual translation workflows, too. If internationalization and localization are priorities for your particular site, you might not want to lean on aria-label
for populating buttons’ names like this.
Instead, you might opt to stick a visually-hidden node inside the button:
<button>
<svg aria-hidden="true"> <!-- “x” icon --> </svg>
<span class="visually-hidden">Close</span>
</button>
In Tandem With an aria-hidden
Node
Generally, we want to avoid micromanaging screenreader pronunciations, especially since many overrides make the user experience worse for braille display users and voice control users. This said, sometimes we might find that we do really need to replace some contents with a screenreader-friendlier alternative. One way we can do this is with a one–two punch of the visible but aria-hidden
node with the visually-hidden, screenreader-friendlier node:
<span aria-hidden="true">
★★★☆☆
</span>
<span class="visually-hidden">
3 out of 5 stars
</span>
Note: Sometimes, developers will try to resolve this by placing an aria-label
on the static text node, such as <span aria-label="3 out of 5 stars">★★★☆☆</span>
. However, unless you’re overriding the node’s role as well, using aria-label
on most static text elements is invalid and therefore not well supported.
Skip Links
Skip links are same-page links designed to help keyboard navigators bypass hefty sections of the page. Most commonly, they’re used to skip over the main site navigation, which is generally repeated on every page, to get the user directly to the main page contents.
There’s no requirements that skip links should be hidden, but many sites opt to hide their skip links and reveal them when they get focused, so as to not clutter the experience for non-keyboard navigators. display: none
would prevent the link from being focusable/
If you hide your skip links, make sure your visually-hidden styles do not hide the links when they’re focused. Otherwise, keyboard navigators wouldn’t be able to tell what they’re focused on or what it would do.
Hiding Live Regions
Developers can use live regions to trigger ad hoc screenreader announcements. However, live regions can be finicky, and so commonly, this takes the form of a single, global, page-wide live region. Given that such ad hoc announcements are generally meant to reinforce status updates that sighted users can see visually, devs generally opt to hide the global live region and leave it screenreader-only.
<div aria-live="polite" id="polite-announcements" class="visually-hidden">
</div>
<div aria-live="assertive" id="assertive-announcements" class="visually-hidden">
</div>
Note: This use case could be addressed someday if the imperative ariaNotify
API proposal ever lands. If and when it lands and is widely supported, sites could invoke ariaNotify()
to trigger announcements on demand, without the need for any live region markup.
External Resources
- The anatomy of visually-hidden, by James Edwards on the TPGi blog
- Inclusively Hidden, by Scott O’Hara
- Visually hidden content is a hack that needs to be resolved, not enshrined, by Scott O’Hara
- Inclusively Hiding & Styling Checkboxes and Radio Buttons, by Sara Soueidan
- Hiding Content Responsibly, by Kitty Giraudel