Skip to content

Instantly share code, notes, and snippets.

@ffoodd
Last active March 21, 2025 09:26
Show Gist options
  • Save ffoodd/000b59f431e3e64e4ce1a24d5bb36034 to your computer and use it in GitHub Desktop.
Save ffoodd/000b59f431e3e64e4ce1a24d5bb36034 to your computer and use it in GitHub Desktop.
Improved .sr-only

Improved .visually-hidden

Theorically bulletproof CSS class for visually hide anything and keep it accessible to ATs.

A Pen by ffoodd on CodePen.

License.

<button type="button">
<span aria-hidden="true">&times;</span>
<span class="visually-hidden">Close</span>
</button>
<a href="/" class="visually-hidden-focusable">Skippy</a>
<div dir="rtl">
<button type="button">
<span aria-hidden="true">&times;</span>
<span class="visually-hidden">Close</span>
</button>
<a href="ffoodd.fr" class="visually-hidden-focusable">ffoodd.fr</a>
</div>
/*
Improved screen reader only CSS class
@author Gaël Poupard
@note Based on Yahoo!'s technique
@author Thierry Koblentz
@see https://www.cssmojo.com/hide-content-from-sighted-users/
* 1.
@note Use to only display content when it's focused, or one of its child elements is focused
@see http://www.w3.org/TR/2013/NOTE-WCAG20-TECHS-20130905/G1
@note Based on a HTML5 Boilerplate technique, included in Bootstrap
* 2.
@note `clip-path` shortest syntax
@author Yvain Liechti
@see https://twitter.com/ryuran78/status/778943389819604992
* 3.
@note preventing text to be condensed
author J. Renée Beach
@see https://medium.com/@jessebeach/beware-smushed-off-screen-accessible-text-5952a4c2cbfe
@note Drupal 8 goes with word-wrap: normal instead
@see https://www.drupal.org/node/2045151
@see http://cgit.drupalcode.org/drupal/commit/?id=5b847ea
* 4.
@note !important is important
@note Obviously you wanna hide something
@author Harry Roberts
@see https://csswizardry.com/2016/05/the-importance-of-important/
*/
.visually-hidden,
.visually-hidden-focusable:not(:focus, :focus-within) {
border: 0 !important;
clip-path: inset(50%) !important; /* 2 */
height: 1px !important;
margin: -1px !important;
overflow: hidden !important;
padding: 0 !important;
width: 1px !important;
white-space: nowrap !important; /* 3 */
}
/*
Prevent visually hidden caption from breaking table's collapsing borders
@author Louis-Maxime Piton
@see https://github.com/twbs/bootstrap/pull/37533
*/
.visually-hidden:not(caption),
.visually-hidden-focusable:not(caption):not(:focus, :focus-within) {
position: absolute !important;
}
/*
Prevent overflowing children from being focusable.
@author Django Janny
@see https://github.com/twbs/bootstrap/pull/41286
*/
.visually-hidden *,
.visually-hidden-focusable:not(:focus, :focus-within) * {
overflow: hidden !important;
}
@sukima
Copy link

sukima commented Aug 22, 2022

Are the better examples because it doesn’t fit in the use of a button since a button can handle the same thing using aria-label

<button type="button" aria-label="Close">&times;</button>

Pretty sure the aria-label will override the text content of a button for screen readers. 🤔

@ffoodd
Copy link
Author

ffoodd commented Aug 23, 2022

It would indeed, but won't:

  1. appear when CSS is disabled for any reason,
  2. be taken into account when selecting the component,
  3. translate when using thing like Google Translate…

There might be more arguments, but you got the picture: I really think visually hidden content is better than ARIA attributes — and it also better respects ARIA rules, since we can use HTML & CSS to do this.

@GlitzSmarter
Copy link

Thank you so much for this. You may want to change the first see link (in the css) to an archive.org copy as the original is no longer available.

@ffoodd
Copy link
Author

ffoodd commented Apr 5, 2023

Thanks @GlitzSmarter for reporting, I managed to find a copy on the author's own blog.

@aquaductape
Copy link

Why position absolute as opposed to fixed?

@bjoernuhlig
Copy link

Why position absolute as opposed to fixed?

@aquaductape https://webaim.org/techniques/css/invisiblecontent/
"position:absolute; tells the browser to remove the element from the page flow and to begin positioning it."

@Mintoo200
Copy link

Mintoo200 commented Dec 18, 2024

Why position absolute as opposed to fixed?

@aquaductape https://webaim.org/techniques/css/invisiblecontent/ "position:absolute; tells the browser to remove the element from the page flow and to begin positioning it."

position: fixed; would also do that :

The element is removed from the normal document flow, and no space is created for the element in the page layout.

MDN – Position

In this case, position: absolute; is (very marginally) better because the element will then be positioned closer to the place it is used in the page. Given that screen readers are used not only by blind users, but users with vision deficiencies more broadly, some users could jump to a visually hidden element. The page wouldn't then scroll because the fixed element is positioned relative to the viewport.

This is a corner case since it would be problematic only with element containing only visually hidden elements.

@keltroth
Copy link

keltroth commented Mar 4, 2025

I recently came across a strange behavior with sr-only.

When an element inside the sr-only element becomes scrollable due to the sr-only, it is also focusable and the focus feels lost... Took me a while to figure it out though...

Here is a simple codepen to try it and an attempt to fix it (overflow: hidden to all children).

What do you think @ffoodd ?

@thiemeljiri
Copy link

thiemeljiri commented Mar 4, 2025

Hi @keltroth. What's your use case to put a scrollable content into the sr-only area? I cannot imagine a case where that would be correct. Definitely not with .sr-only which is also applied to a focused element or an element with focus within like the code in this thread. (I actually always implement this class in a way that it's not applied to a focused element or element with focus within as there is no case where that would be correct. Focused element must be always visible to the user.)

Also, scrollable regions should be ideally always focusable (unless you are certain that there always is a focusable element inside it, but ideally even in such case). We're usually doing this manually via setting tabindex="0" to that element, often combined with some role like role="region". Read these resources to understand why:

Thanks to your post, I found out that some browsers actually started making scrollable regions scrollable automatically. As this is crucial for keyboard users (see the links above for explanation), this is something you have to respect. Read this article where this feature is mentioned:
https://cassey.dev/til/2019-11-19-overflow-scroll-gets-focus/

To sum it up, your use case or intention is most likely not OK. Please provide more information to help us guide you to find a correct solution for your case.

@ffoodd
Copy link
Author

ffoodd commented Mar 4, 2025

@keltroth If I sum this case up, since scrollable elements become focusable, any child in . sr-only that'd become scrollable will also become focusable.

I can't see any other workaround than yours, for now.

FWIW, as @thiemeljiri said, .sr-only is usually used on text-level elements. Hiding complex content this way should be avoided.

@keltroth
Copy link

keltroth commented Mar 4, 2025

I came to the same conclusion. Thanks !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment