My Struggle with Screen Reader Only Text for Links— How to reach AA accessibility for external links?

Background

A problem I try to solve: have text to only be picked by the screen reader for accessibility. The text must be visually hidden and does not create unwanted space.

This is a common problem that most developers have encountered for every project that tries to reach AA accessibility standard.

A use case: a link opens in a new window/tab.

According to W3C standard (https://www.w3.org/TR/WCAG20-TECHS/G201.html), the requirement for a link that opens in a new window/tab must pass the following 2 checks:

Check that there is a warning spoken in assistive technology that this link opens to a new window.

Check that there is a visual warning in the text that this link opens to a new window.

Web Accessibility

Solutions I Tried

1. The Most Common Solution

Below CSS class is the most common solution I found online for visually hidden text:

/* https://github.com/h5bp/main.css */
.sr-only {
border: 0;
clip: rect(0, 0, 0, 0);
height: 1px; margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
white-space: nowrap;
width: 1px;
/* 1 */
}

HTML:

<a href="/some-cool-page" target="_blank">
I am a link with visually hidden text
<span class="sr-only">Opens in a new window</span>
</a>

However, when the QA tested with different voice-over software, it is reading the screen only text in a different order in macOS as described https://github.com/h5bp/main.css/issues/12.

Expected to read from macOS voiceover: I am a link with visually hidden text Opens in a new window.

Actual: Opens in a new window I am a link with visually hidden text.

Does not work on MacOS

This is a strange issue because it works on my MacBook (MacBook Pro Mid 2015); however, it failed on the QA’s Macbook (MacBook Pro 2018).

This happens because that class sr-only uses position: absolute. For macOS, whenever using position: aboluste, it is going to read first. This leads to the next solution I attempted.

2. Use “position: relative"

This means I could have to use position: relative. After a few rounds of testing out many different CSS style combinations, this class satisfies our supported browsers (latest Chrome, last Firefox, Windows IE Edge, Windows IE 11) and does not create unwanted spacing around the text:

.sr-only {
border: 0;
height: 0;
width: 0;
margin: 0;
padding: 0;
font-size: 0;
overflow: hidden;
white-space: nowrap;
position: relative;
float: left;
}

However, it failed specifically on Safari with macOS voice over on MacBook Pro 2018.

Does not work on Safari

It is read the text in the wrong order for a specific case such as:

<a href="/some-cool-page" target="_blank">
<div class="flex">
<img src="some-cool-image.png" alt="some cool image">
<p>I am a link with visually hidden text</p>
</div>
<span class="sr-only">Opens in a new window</span>
</a>

Expected to read from screen reader: some cool image I am a link with visually hidden text Opens in a new winodw.

Actual: Opens in a new window some cool image I am a link with visuallly hidden text.

This is still going to read Opens in a new window first. Even though it is below all other text, but its tag is at the top of the HTML DOM tree.

To make it work, I have to change the HTML to something like:

<a href="/some-cool-page" target="_blank">
<div class="flex">
<img src="some-cool-image.png" alt="some cool image">
<p>
I am a link with visually hidden text
<span class="sr-only">Opens in a new window</span>
</p>
</div>
</a>

However, for this solution, for every link, I need to find where the lowest DOM tree node is and place the screen reader only text there, which is a lot of work to maintain.

It does not work well with my angular app if I am using the content projection like below. You do not know where the lowest DOM tree node at this component:

<a href="/some-cool-page" target="_blank">
<div class="flex">
<ng-content select="img"></ng-content>
<ng-content select="p"></ng-content>
</div>
</a>

3. Add “aria-label” to Icons

Remember the 2 accessibility checks for a link that opens a new window/tab. We need to have a “visual warning”, such as an icon:

New Window Icon

If you are using Google material icons, the HTML would look like:

<a href="/some-cool-page" target="_blank">
<div class="flex">
<img src="some-cool-image.png" alt="some cool image">
<p>
I am a link with visually hidden text
<i class="material-icons">
open_in_new
</i>
</p>
</div>
</a>

We could add aria-label on the icon tag:

<a href="/some-cool-page" target="_blank">
<div class="flex">
<img src="some-cool-image.png" alt="some cool image">
<p>
I am a link with visually hidden text
<i class="material-icons" aria-label="Opens in a new window">
open_in_new
</i>
</p>
</div>
</a>

This way, I don’t even need the CSS class sr-only to visually hide the screen reader only text. For most icons, it is usually after the link text in the design and this will make it lowest on the DOM tree.

It works!

There is another neat solution I found online: https://medium.com/@svinkle/why-let-someone-know-when-a-link-opens-a-new-window-8699d20ed3b1.

It uses aria-describedby. The HTML would look like:

<a href="/some-cool-page" target="_blank" aria-describedby="open-in-new-window">
<div class="flex">
<img src="some-cool-image.png" alt="some cool image">
<p>
I am a link with visually hidden text
<i class="material-icons" aria-hidden="true">
open_in_new
</i>
</p>
</div>
</a>
<p hidden id="open-in-new-window">Opens in a new window</p>

Conclusion

Here are my struggles with the screen only text for the links open in a new window use case. Keep site accessible is not easy. The solution changed from one approach to a different one. I need to rewrite some components completely. It turns out I do not change any CSS for a solution at the end.

💁🙋

A frontend web developer in Toronto

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store