For years I've been writing semantic HTML ensuring that the HTML output makes sense in a text-only browser and then trusting that screen-reads would be able to understand the content if it's semantically correct, was this assumption still correct? Historically JAWS (Job Access with Speech) was the primary screen-reader by market share and it was the go to screen-reader for testing. Now with the rise of NVDA (Non-Visual Desktop Access) on Windows (free to use) and VoiceOver on Mac screen-reader the market is more fragmented, with these three making up roughly 90% of the market. On one hand, this is great news for web-users as it means they have more choice, on the other it also means headaches for developers as screen-readers don't always read HTML consistently.

Screen-reader software

As a Mac user I have access to VoiceOver which is the de facto Mac screen-reader, for Windows I use a VM with JAWS, NVDA and every now and again Narrator (which is Windows built in screen-reader)

Note: screen-reader versions; JAWS 2020, VoiceOver Catalina, NVDA 2019.2.1, Narrator Win10

Screen-reader reading modes

Screen-readers are very complex beasts so without going into too much detail I'm just looking at 'Browse' and 'Focus' modes. 'Browse' (aka Virtual) mode is when the screen-reader reads out paragraph text e.g. web-page content and 'Focus' mode is when the user tabs through focusable elements e.g. a navigation or input form.

Firstly, a couple of developer/tester tips

Just as a side note it's very handy when testing a web-page to have a visual log of what the screen-reader has just readout. With NVDA you can view this by enabling the 'NVDA Speech Viewer' from the tools menu, in JAWS enable 'Speech history' then you can press INSERT+SPACE, followed by H key to view history and with VoiceOver the transcript is shown in the VoiceOver panel by default. If you are a Mac user testing on a VM, I recommend plugging in a standard Windows keyboard. True it's possible to configure screen-readers to use laptop keyboard and you can map keys using something like SharpKeys but personally I find having a real Windows keyboard way, way easier!

Here is a download document link example comprising of a document title ("My Document") with additional "download PDF" text we want screen-readers to read out, but visually we just want to show a download icon. I created a few HTML options to see how different screen-readers read them out. All the following links (apart from the control) I styled to display basically the same, either by using an inline graphic, fontawesome icons or background image on the link element and visually hiding the "download PDF" text where applicable. So all the links tested below look like this... Screenshot of Download document link

Expected output is; "My Document download PDF" (preceded or followed by 'link')
Focus mode Browse mode
HTML example
JAWS, Chrome
JAWS, Edge 18
JAWS, Edge 79
NVDA, Firefox
NVDA, Edge 18
NVDA, Edge 79
Narrator, Firefox
Narrator, Edge 18
Narrator, Edge 79
VoiceOver
JAWS, Chrome
JAWS, Edge 18
JAWS, Edge 79
NVDA, Firefox
NVDA, Edge 18
NVDA, Edge 79
Narrator, Firefox
Narrator, Edge 18
Narrator, Edge 79
VoiceOver
<!-- control link -->
<a href="./document.pdf">
  My Document download PDF
</a>
As expected As expected As expected As expected As expected As expected As expected As expected As expected As expected As expected As expected As expected As expected As expected As expected As expected As expected As expected As expected
<!-- 1. semantic text -->
<a href="./document.pdf">
  My Document
  <span class="sr-only">,
    download PDF
  </span>
</a>
As expected (See note on semantic text) "link My Document link download PDF" As expected As expected As expected As expected As expected As expected As expected As expected As expected As expected As expected As expected "link My Document link download PDF" As expected As expected As expected As expected As expected
<!-- 2. aria-label -->
<a href="./document.pdf"
  aria-label="My Document download PDF">
  My Document
  <span class="fa fa-download"></span>
</a>
As expected As expected As expected As expected As expected As expected As expected As expected As expected As expected As expected As expected As expected As expected As expected "My Document" "link My Document" "link My Document" "My Document" As expected
<!-- 3. aria-label on icon -->
<a href="./document.pdf">
  My Document
  <span aria-label="download PDF"
    class="fa fa-download"></span>
</a>
"My Document link" "My Document link" As expected As expected "My Document link" As expected As expected As expected As expected As expected "Link My Document" "Link My Document" "Link My Document" As expected "link My Document" "My Document" "link My Document" "link My Document" "My Document" As expected
<!-- 4. aria-label and semantic icon -->
<a href="./document.pdf"
  aria-label="My Document download PDF">
  My Document
  <span class="fa fa-download">
    <span class="sr-only">download PDF</span>
  </span>
</a>
As expected "link My Document download PDF link MyDocument download PDF link Download PDF" As expected As expected As expected As expected As expected As expected As expected As expected As expected "link My Document download PDF link MyDocument download PDF link Download PDF" As expected "link My Document download PDF link MyDocument download PDF" As expected As expected As expected As expected As expected
<!-- 5. title on link -->
<a href="./document.pdf"
  title="download PDF">
  My Document
  <span class="fa fa-download"></span>
</a>
As expected "link My Document download PDF link MyDocument download PDF link Download PDF" As expected As expected As expected As expected As expected As expected "link My Document" As expected As expected "link My Document download PDF link MyDocument download PDF link Download PDF" As expected As expected "link My Document download PDF link MyDocument download PDF" "My Document" As expected As expected "My Document" As expected
<!-- 6. CSS content -->
<style>
.pdf::after {
  content: ", download PDF";
}
</style>
<a href="./document.pdf" class="pdf">
  My Document
</a>
As expected As expected As expected As expected As expected "My Document link" As expected "link My Document" "link My Document" As expected As expected "link My Document link Download PDF" As expected As expected "link My Document link download PDF" As expected As expected As expected As expected As expected
<!-- 7. SVG icon -->
<a href="./document.pdf">
  My Document
  <svg version="1.1">
    <text class="sr-only">download PDF</text>
    <path d="..." />
  </svg>
</a>
"My Document link" As expected As expected See note on inline SVG See note on inline SVG As expected "link My Document" As expected See note on inline SVG As expected As expected "link My Document link download PDF" As expected See note on inline SVG "link My Document link clickable download PDF" As expected See note on inline SVG As expected As expected As expected
<!-- 8. IMG icon -->
<a href="./document.pdf">
  My Document
  <img src="..." alt="download PDF">
</a>
As expected As expected As expected "My Document download PDF graphic link" "My Document link" As expected As expected As expected As expected As expected As expected "link My Document link graphic download PDF link" As expected "link My Document graphic download PDF" "link My Document graphic clickable download PDF" "link My Document graphic clickable download PDF" "link My Document download PDF image" "link My Document download PDF image" "My Document" As expected

One other thing while we're at it... screen-readers create lists of links which is a very handy way for users to find content. So lets have a quick look at how our links behave when they are listed...

Expected output is; "My Document download PDF"
HTML example
JAWS, Chrome
JAWS, Edge 18
JAWS, Edge 79
JAWS, Firefox
NVDA, Chrome
NVDA, Edge 18
NVDA, Edge 79
NVDA, Firefox
Narrator, Chrome
Narrator, Edge 18
Narrator, Edge 79
Narrator, Firefox
VoiceOver
<!-- control link -->
<a href="./document.pdf">
  My Document download PDF
</a>
As expected As expected As expected As expected As expected As expected As expected As expected As expected As expected As expected As expected As expected
<!-- 1. semantic text -->
<a href="./document.pdf">
  My Document
  <span class="sr-only">,
    download PDF
  </span>
</a>
As expected As expected As expected As expected As expected As expected As expected As expected As expected As expected As expected As expected As expected
<!-- 2. aria-label -->
<a href="./document.pdf"
  aria-label="My Document download PDF">
  My Document
  <span class="fa fa-download"></span>
</a>
As expected As expected As expected As expected As expected My Document As expected As expected As expected As expected As expected As expected As expected
<!-- 3. aria-label on icon -->
<a href="./document.pdf">
  My Document
  <span aria-label="download PDF"
    class="fa fa-download"></span>
</a>
My Document As expected As expected My Document As expected My Document As expected As expected As expected As expected As expected As expected As expected
<!-- 4. aria-label and semantic icon -->
<a href="./document.pdf"
  aria-label="My Document download PDF">
  My Document
  <span class="fa fa-download">
    <span class="sr-only">download PDF</span>
  </span>
</a>
As expected As expected As expected As expected As expected As expected As expected As expected As expected As expected As expected As expected As expected
<!-- 5. title on link -->
<a href="./document.pdf"
  title="download PDF">
  My Document
  <span class="fa fa-download"></span>
</a>
My Document My Document As expected My Document My Document My Document As expected My Document My Document My Document As expected My Document My Document
<!-- 6. CSS content -->
<style>
.pdf::after {
  content: ", download PDF";
}
</style>
<a href="./document.pdf" class="pdf">
  My Document
</a>
As expected My Document As expected As expected As expected As expected As expected As expected My Document My Document As expected As expected My Document
<!-- 7. SVG icon -->
<a href="./document.pdf">
  My Document
  <svg version="1.1">
    <text class="sr-only">download PDF</text>
    <path d="..." />
  </svg>
</a>
As expected As expected As expected As expected As expected As expected As expected As expected As expected As expected As expected My Document As expected
<!-- 8. IMG icon -->
<a href="./document.pdf">
  My Document
  <img src="..." alt="download PDF">
</a>
As expected As expected As expected As expected As expected As expected As expected As expected As expected As expected As expected As expected As expected

What does the above mean? Basically you can expect your HTML to be readout differently depending on the reading mode and screen-reader + browser combination. Skip to conclusion

Note on semantic text

For screen-readers you can add a comma space to hidden text, screen-readers will then add a very small pause between the words. This will also prevent the start of the hidden text from merging with the end of the previous text, which can sometimes happen!

(I use Bootstrap screen-reader utility classes)

<a href="./document.pdf">
  My Document
  <span class="sr-only">, download PDF</span>
</a>

SVG support

The SVGs themselves are widely supported and vector graphics should be preferred over images, but lets just have a quick look at the following three inline SVGs which use the child Title, Description (desc) and Text elements to see which is readout by the screen-reader. The expected output is for the links is "link Document download" or "Document download link" and for the buttons "Close button" or "button Close" .

HTML example
JAWS, Chrome
JAWS, Edge 18
NVDA, Firefox
NVDA, Edge 18
Narrator, Firefox
Narrator, Edge 18
VoiceOver
<a href="#">
  Document <svg><title>dowload</title> ... </svg>
</a>
<a href="#">
  Document <svg><desc>dowload</decs> ... </svg>
</a>
<a href="#">
  Document <svg><text>dowload</text> ... </svg>
</a>
Title As expected As expected Fail Fail Fail As expected Fail
Description Fail As expected Fail Fail Fail As expected As expected
Text As expected As expected As expected As expected Fail As expected As expected
<button>
  <svg><title>Close</title> ... </svg>
</button>
<button>
  <svg><desc>Close</decs> ... </svg>
</button>
<button>
  <svg><text>Close</text> ... </svg>
</button>
Title As expected As expected Fail As expected Fail As expected As expected
Description Fail As expected Fail As expected Fail As expected As expected
Text Fail As expected As expected As expected Fail As expected As expected

Well, the interesting thing here is that the different elements where readout or ignored depending on the parent element, the conclusion of the above is that all three of these elements are best avoided! Instead I would include semantic text outside the SVG element and tell the screen-reader that the SVG is decorative by adding role="presentation" or aria-hidden="true" e.g.

...
  <span class="sr-only">, download PDF</span>
  <svg aria-hidden="true">
    <!-- don't use title, desc or text here! -->
  </svg>
...

Points to take-away...

  • Screen-readers DO NOT readout content consistently
  • I always assumed that a button/link containing an image with alt text would be a safe choice of markup, as it works nicely in a text only browser, but actually the alt text can be completely ignored in some screen-readers + browser + reading mode combinations
  • Support for inline SVG child elements, title, desc and text elements is 'patchy' to say the least! Avoid
  • aria-label isn't a silver bullet, it won't always be read depending on the reading mode + browser combination and if the element is a role based element or not
  • CSS content won't always be read and is probably best avoided for anything important
  • The Title attribute doesn't get readout in all scenarios, so again, probably best avoided for important information
  • Add leading comma space ', ' to semantic text
  • Sometimes screen-readers will duplicate content and read it or bits of it twice, whilst this is irritating, it's not a show stopper as it is still clear what the link/button does and what will happen if it's clicked
  • When testing it's best to test on a high frequency screen-reader browser combination like JAWS with Chrome or NVDA with Firefox as if an unusual combination
  • Semantic HTML is still a safe option. Although, some screen-readers will read the elements separately e.g. "link (link text) link (semantic text)"

In conclusion

The almost 'bullet proof' link... whilst the semantic link is not 100% perfect as it causes some screen readers to repeat the word 'link' it does at least have the 'download PDF' text added to all our test cases including the links list. Whichever graphic format, background-image, icon, img or SVG you choose (for me, an inline SVG would be the best choice, but that's another story!) hide it from the screen-reader by using the aria-hidden="true" or role="presentation" attributes. The aria-label attribute isn't necessary on semantically correct content.

<!-- semantic text with icon front -->
<a href="./document.pdf">
  My Document
  <span class="sr-only">, download PDF</span>
  <i class="fa fa-download" aria-hidden="true"></i>
</a>
<!-- semantic text with inline image -->
<a href="./document.pdf">
  My Document
  <span class="sr-only">, download PDF</span>
  <img aria-hidden="true" src="..." alt="" />
</a>
<!-- semantic text with inline SVG -->
<a href="./document.pdf">
  My Document
  <span class="sr-only">, download PDF</span>
  <svg role="presentation">...</svg>
</a>

Further reading