Menu

Settings

Theme
Animations

One of the strangest artifacts of web accessibility to me is the .visually-hidden utility class. You might also know it as .sr-only (or possibly as .screen-reader-text, .element-invisible, or any number of other names throughout the ages).[1]

Conventional ways to hide elements include the styles display: none or visibility: hidden, or using HTML's hidden attribute. When you use any of these approaches, the browser assumes no one is meant to access those elements, and it won't expose that content to assistive technologies. This makes for a bit of a conundrum: what if you want to hide something visually, but still expose it to assistive technologies such as screenreaders? For instance…

Enter, the .visually-hidden utility class, which usually (foreshadowing!) looks something 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;
}

I've borrowed this particular set of rules from Kitty Giraudel's .sr-only snippet. These rules make the applied element take up zero space, without triggering any of the heuristics browsers use to exclude elements from assistive technology output. To learn more about how these styles work, I recommend checking out James Edwards's breakdown of .visually-hidden.

This snippet of styles works really well, and has been iterated upon and vetted by accessibility practitioners over the years. However, it would be a big win for the web to have a native HTML or CSS technique, no copypasta required, to visually hide elements while still exposing them to assistive technologies.

Problems with Copypastas

It's a Bundle of Hacks

Or, at least, it feels that way.

After all, the very reason for these styles is to make an element as hidden as possible without also triggering browsers' heuristics for hiding elements from assistive technologies. The various rules could each constitute a point of failure if browsers adjust their heuristics, whether through conscious decision or as a bug. If a browser, for instance, stopped exposing content obscured by overflow: hidden to assistive technologies, this ruleset would be toast. Ideally, browsers wouldn't just break stuff like that — I don't think there's a big chance this would happen — but it would be safer not to rely on browser regression so much.

Which Snippet Should You Use?

As my friend Evan noted, there's not just one set of visually-hidden rules out there:

@ben there’s also more than one magical copy-pasta floating around out there, and I’m never entirely sure which one I should use.

Evan (@darth_mall@notacult.social)

There have been lots of takes on visually hidden styles over the years. Some are homegrown, unvetted, and brittle, such as one I found recently which leaned in large part on color: transparent, which would be ignored in forced colors mode, causing invisible text to become visible again. Other approaches were once commonplace but are now discouraged — namely, an approach which put the contents very far off screen with absolute positioning or a large negative text-indent. This is discouraged nowadays because it caused issues for right-to-left content,[3] people using screen magnifiers, sighted screenreader users who benefit from seeing their screenreader's cursor, and mobile screenreader users who rely on touch.

While today, accessibility practitioners have landed on the clip-paths-and-hidden-overflows approach provided above, there are still iterations and variations on the theme, all attempting to deal with new quirks or contexts faced in the real world. For instance, Kitty Giraudel's visually-hidden styles are pretty similar to those shared by Scott O'Hara, except that Kitty also removes the element's border, padding, and margins. These additions make the visually-hidden styles more robust in edge-casier scenarios such as parents with overflow: auto.

Given that we're still occassionally finding quirks that need to be accounted for, how do we know that the visually-hidden styles that have been sitting in our codebase for a few years (including those provided by third-party libraries and CSS frameworks) are the latest, greatest, up-to-datest iterations? How should accessibility practitioners communicate incremental updates to the webdev community at large?

What I'd Love to See

Given there's a market for visually hiding content while still exposing it to assistive technologies, the web would benefit greatly from providing a native, spec'd out CSS/HTML technique that doesn't require copying and pasting a magic snippet. And for my money, the approach I'd most love to see is…

/* Hypothetical */
display: visually-hidden;

…defined as identical to display: none, except that this style doesn't disqualify the element from being exposed to assistive technologies.[4]

In the wild, this would probably lead to style declarations like:

.skip-link:not(:focus):not(:active) {
display: visually-hidden;
}

And I think using it with Sass or CSS nesting would feel very clean:

.skip-link {
/* Put your fancy styles for when the link is visible here */

&:not(:focus):not(:active) {
display: visually-hidden;
}
}

Depending on how closely this hypothetical display: visually-hidden matches the behavior of display: none, this would have another leg up on the .visually-hidden styles in that text hidden this way wouldn't show up in Ctrl+F in-page searches or get added to the clipboard, either of which could be unexpected and unintuitive for sighted users.

What Are The Options?

And why do I think display: visually-hidden is the way to go?

When I've asked about proposals for native visually-hidden on Twitter before, I've gotten a myriad of different potential implementations folks would like to see.

Put It In HTML!

Some responses I've gotten propose adding some attribute or new attribute value to HTML.

In some cases, people suggested adding a new value to the hidden attribute (maybe something like hidden="visually"?), since hidden is no longer a simple boolean. I think this would be a dangerous route to go down (in my mind, a complete nonstarter), because the failure mode for a browser that doesn't recognize the new value yet would be to exclude the content from assistive technology output altogether.

Additionally, in the early days of the hidden attribute, many stylesheets added this rule to be able to start using hidden earlier.

[hidden] {
display: none;
}

This rule is still present on many sites, and in CSS resets and normalizers such as normalize.css. Sites with a rule like this would still prevent visually-hidden content from being exposed to assistive technologies.

This brings us to a larger point about putting this in HTML: even if we were to create some sort of brand new visuallyhidden attribute, browsers' default styles for HTML elements and attributes are incredibly easy to override. All it takes is a well-meaning developer to write…

[visuallyhidden] {
display: none;
}

…and the attribute is toast for a site in a way that most developers would totally miss.

Additional concerns I have around putting this in HTML are that:

  • It doesn't really facilitate responding to the viewport or container size without JavaScript (I could see wanting to use media queries or container queries to show/hide the text inside of an icon button at various viewport widths or container sizes, for instance)
  • It doesn't really facilitate responding to states such as hover or focus without JavaScript either (consider the visually-hidden skip link which appears when it receives keyboard focus)
  • The notion of something being "visually" hidden is a very presentational, browser-centric idea, and I'm not sure how well these semantics would translate to other user agents

In my mind, if we're going to have a native approach to visually hiding some element, it's gotta be in CSS.

Make a New CSS Property!

I could definitely live with this!

This would require a bit of bikeshedding on the name, because naming is hard, but for the purposes of this article, let's say it looks something like:

/* Hypothetical */
element-visibility: visually-hidden;

This gives us something that we can use along with all of our media queries, container queries, pseudo-classes, and more!

/* Hypothetical */

.skip-link:not(:focus):not(:active) {
element-visibility: visually-hidden;
}

@media only screen and (max-width: 600px) {
button.icon-button > span {
element-visibility: visually-hidden;
}
}

This is absolutely a step in the right direction for me!

A weakness of a new property like this, however, is that it's not entirely clear to me how this would resolve:

element-visibility: visually-hidden;
display: none;

Would the element still be hidden from assistive technologies because of the display: none?

Or what about this?

element-visibility: visually-hidden;
display: block;

Would the element be visually hidden here, or would the display: block unhide it? Could we articulate why that behavior is the way it is?

I'm sure the specs would settle on an order of precedence here, but I'm not sure that precedence could ever be completely intuitive to a developer. In my opinion, that just increases the risk that some content might not get exposed to assistive technologies that's intended to be.

I think introducing a new property also comes with an education cost. CSS already has display, visibility, content-visibility, and other ways to hide an element, which is already a confusing jumble of similar names and functionality. If we introduce a new property, not only do we have to figure out how it interacts with the other CSS ways to hide something… we also add to the confusing jumble, which I feel would just make this wing of CSS just that much less approachable to newcomers.

If we want to avoid adding a new, similarly named CSS property to the pile and reduce how many other properties this could conflict with,[5] then it seems to me that the way to go is to add a new value to a property that already exists. Since in my mind, the ideal functionality is like display: none except without hiding content from assistive technologies, a new display property makes the most sense to me. Hence, display: visually-hidden!

The Road Ahead

An addition like this would go through the CSS Working Group. Back in 2016, Sara Soueidan filed an issue recommending exactly this, but the conversation seems to have stagnated. If a display: visually-hidden seems appealing to you, give it a thumbs up and consider contributing to the conversation.

If display: visually-hidden ever does go through, I think it's worth being strategic about how we start to incorporate it into our sites (given that "evergreen" doesn't mean immediately available). For my money, the simplest and most robust approach would be to add the rule to our already-working .visually-hidden/.sr-only classes:

.visually-hidden:not(:focus):not(:active) {
/* Hypothetical */
display: visually-hidden;

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

…at least until our browser stack has reliably supported this value for some time.

Conclusion

While the .visually-hidden/.sr-only utility styles have proven incredibly useful, the web would absolutely benefit in the long run from enshrining a native approach into the web standards. Such a rule would be easier to teach and easier to incorporate with CSS's responsive and stateful features. It'd also discourage unvetted, homegrown approaches from popping up, and it'd ensure that developers wouldn't need to update their projects' styles every few years when an improvement is discovered.

update: Since publishing this article, a few people have published their responses! I'd especially recommend giving Scott O'Hara's response a read. It goes into some of the problems that would emerge from enshrining the .visually-hidden technique into the standards, and pitches a few improvements the web could make that would mitigate the need for visually hiding in the first place.

More CSS Wishlists

This article is inspired in part by Dave Rupert's CSS wishlist for 2023. Several other people have put out their own CSS wishlists, and you should give them a read!


Footnotes

  1. ".sr-only," short for "screenreader only," is a widely used name for these styles, and would be especially familiar to Tailwind users and to Bootstrap users before them. WordPress, meanwhile, uses the name ".screen-reader-text." The name ".element-invisible" was once used in Drupal, but was deprecated and replaced with ".visually-hidden" to better align with the HTML5 Boilerplate.

    Although it's common to explicitly reference screenreaders in the classname, I personally recommend using the name ".visually-hidden," since content hidden this way would also be exposed to other assistive technologies, as well as be surfaced in browser Reader Modes or RSS readers, and so "screenreader-only" is a bit of a misnomer. |

  2. The one-two punch of an aria-hidden node for the visual treatment along with a .visually-hidden node with a clearer experience for assistive technologies is pretty common. I've seen this approach used for icon buttons, expanding abbreviations and timestamps, providing an unsplit alternative for split text, or substituting punctuation that gets inconsistently announced by screenreaders out for its spelled-out name in signup form help text. Use with care — the assistive technology experience shouldn't diverge too much from the visual experience. |

  3. This concern largely comes from a time before logical properties. Were it not for the other concerns with offscreen positioning, a modern solution could have been to absolutely position the element with inset-inline-start. |

  4. I've worded it this way, and not as "the element is exposed to assistive technologies," because other factors would still exclude the element from the accessibility tree, such as the hidden and aria-hidden attributes. It's not that this style would guarantee the element is in the accessibility tree; just that visually hiding the element would not be what prevents it from being exposed. |

  5. You'd definitely still get conflicts if you were to, say, combine display: visually-hidden and visibility: hidden. This would reduce the combinatorics here, but not eliminate them. |