This post was originally published on September 6 2020. It was last updated on September 9 2020 (11:53 am).
Reading time: 16 minutes
Introduction
I recently discovered a great webinar on Tenon’s YouTube channel.
The webinar discussed a situation which I could readily identify with – screen reader verbosity when announcing linked cards.
This post explains what a card is and why this common problem exists. I describe the thought process behind building a card. Then I demonstrate how I reworked an overly verbose linked card into a much more succinct and accessible one.
Table of contents
This post is really long, so I’ve split it into seven parts:
- Part 1: What is a card, in which I describe the card design pattern
- Part 2: Anatomy of a card, which dissects the HTML structure of a card
- Part 3: Verbose card, where I discuss a coded example of an inaccessible card
- Part 4: Verbose card with tweaks, where I make several attempts to improve the accessibility of the verbose card, without changing its basic structure
- Part 5: Succinct card, where I discuss a coded example of a more accessible card
- Part 6: Comparison of Verbose and Succinct cards, where I briefly compare the two cards
- Part 7: Summary, where I wrap up and discuss what I learned in the process of troubleshooting and fixing my code
If you don’t have a lot of time, I recommend skipping straight to Part 5.
Part 1: What is a card?
A ‘card’ is a type of design pattern which is used in many modern website interfaces.
The card is typically presented as a rectangular box. This box contains one or more pieces of brief content, and is often combined with at least one hyperlink.
When used with a hyperlink, the purpose of the card is to act as a ‘teaser’ for the page to which it links. The goal is to summarise the key information present in the linked page, in a readable and visually appealing way. Users can quickly tell whether it’s worth clicking through to the linked page or not.
Smashing Magazine show a great variety of cards in their article Designing Card-Based User Interfaces.
Part 2: Anatomy of a card
The card structure and layout is typically stored in a template.
This template is used by a Content Management System (CMS) to assemble related pieces of data stored in a database. This data includes text strings and boolean options for whether certain elements appear or not. This allows the same anatomy to be reused, with different sets of data.
I’ve created several code examples to demonstrate the anatomy of a typical card. This is based on a card pattern which I recently implemented.
Content structure
Good content structure is essential for good accessibility. This means using a logical content order and the appropriate semantic HTML5 elements.
My card used the following pieces of content. The semantic HTML5 elements are shown in brackets:
- Title (
h2
) - Picture (
img
) - Publication date (
p
) - Description (
p
) - Category tags subtitle (
h3
) - Category tags list (
ul
)
These HTML5 elements are technically known as flow content. Flow content can contain text or embedded content. Each HTML5 element was chosen for a reason.
Title (h2
)
The first element is the title. Marking this up as an HTML heading is beneficial to screen reader users, who are guided through the visual page by an auditory representation of the underlying structure. HTML headings can be quickly accessed using special keyboard shortcuts. This is visually equivalent to scanning the page for headlines. From the heading, screen reader users can easily drill down to the content below.
Image (img
)
The picture is an inline img
. This is exposed to non-sighted screen reader users via the alt
attribute. The value of this attribute is dependent on the way that the card link is implemented. This is discussed in implications for linked images, later on.
Publication date (p
)
The publication date is a simple string of letters and does not use any special semantics. However it meets the specification of a block of text separated from adjacent blocks by blank lines, so a p
element is preferable to a generic div
or span
.
Description (p
)
The description is a longer string, but is semantically equivalent to the publication date.
Category tags subtitle (h3
)
The category tag list is preceded by a subtitle. This is an h3
, to indicate that it is a child of the preceding h2
. The subtitle describes the adjacent list items as tags. This is intended solely for non-sighted users who can’t see the typical tag styling.
Category tags list (ul
)
The tags are marked up in a list. This allows screen reader users to understand how many items are in the list. Screen readers provide special keyboard shortcuts to easily navigate through the list items.
Layout order
With a solid content structure in place, it’s time to turn this into something visually appealing.
My styling is authored in SCSS, a superset of CSS which has some special features useful for developers. This is applied to the HTML5 structure using popular BEM (Block Element Modifier) selector syntax. Codepen compiles the SCSS to standard CSS (Cascading Style Sheets).
Aside from making the card more visually attractive, a key job of the CSS is to alter the layout order. This is done using CSS Flexbox layout, which has support for the order
property. I could also have used CSS Grid layout here.
The order is set as follows:
- Title (
order:2
) - Picture (
order:0
) - Publication date (
order:1
) - Description (
order:2
) - Category tags subtitle (
order:2
) - Category tags list (
order:2
)
This results in the picture appearing first, followed by the publication date, followed by the remainder of the elements in their structural order.
Altering the order in the CSS layout rather than the HTML locks in the accessibility benefits of the underlying structure.
Warning: Use order
with care
Note that this re-ordering technique must be used carefully, as it can lead to user disorientation if links are involved.
Part 3: Verbose card
Code example
My first attempt at the card is shown in the pen below.
External accessibility review
This card failed an external accessibility review.
The markup in question was a revision of an existing pattern in a custom framework. While I wasn’t directly responsible for the legacy code, I still felt very ashamed of the result.
The reviewer noted that:
The list boxes have a confused operation with screenreader. The various elements do not come together to give a clear or consistent experience.
The mix of heading, alt text, link and supporting text provides for a confusing experience to screenreader users. Adaptive technology cannot make a consistent decision on now to present information.
Suggested that the graphics be treated as decorative. Determine whether the heading adds to the experience and code such that there is a pause between it and the following text.
External accessibility reviewer
Manual accessibility testing
Anxious to understand this criticism better, I set about doing some manual testing.
Opening up my Macbook Pro, I opened the page in Safari web browser and started Apple’s VoiceOver screen reader. Using Rotor, I brought up a list of all the links on the page.
Test: Read link from Links Menu (macOS VoiceOver + Safari)
- Select System Preferences: Accessibility: Preferences: VoiceOver: Enable VoiceOver
- Press CONTROL + OPTION + U, to open Rotor
- Press RIGHT ARROW, until you hear “Links Menu”
- Press UP ARROW or DOWN ARROW, to read each link in the list
Result
Adjusted to the example above, this is what VoiceOver read for the card link:
Link. Verbose card link Placeholder image which reads four hundred ex three hundred. Saturday the fifth of September two thousand and twenty twenty An HTML five block link wraps the card’s contents. This creates verbose link output in screen readers. Tagged as tag one tag two tag three.
VoiceOver’s rendition of the Verbose card link
Then I opened up my Windows 10 laptop and tested in Internet Explorer 11 using NVDA to access the same link in the Elements List.
Test: Read link from Elements List (NVDA + IE11)
- Press NVDA key (e.g. CAPSLOCK) + F7, to open the Elements List
- Press ALT + K to check the “Links” radio button
- Press TAB to focus the tree view
- Press UP ARROW or DOWN ARROW until the desired link has focus
Result
Verbose card link Placeholder image which reads four hundred ex three hundred dot Sat zero five dot zero nine dot two thousand and twenty an HTML five block link wraps the card’s contents. This creates verbose link output in screen readers dot tagged as bullet tag one bullet tag two bullet tag three.
NVDA’s rendition of the Verbose card link
Wow, both screen readers were really verbose. This was the exact opposite of the efficient navigation experience which I wanted to provide to screen reader users. It was clear that this approach would make it tiring to navigate the page. I would be adding a significant cognitive load, for users who were already disadvantaged.
Why is this verbosity bad?
Before proceeding, I want to clarify why this level of verbosity was bad. A shared understanding of the problem helps determine the best way to solve it.
Verbosity is when the screen reader says more rather than less. This is actually normal behaviour for a screen reader. Users can configure their screen readers to be more or less verbose. This provides them with more or less detail about elements in the page.
This is good verbosity, because the user is in control of it. They can configure their screen reader to be more or less verbose. They can choose which elements they navigate to.
A developer’s job is not to dumb down the content, so that the page is too simplistic. Instead, pages should be designed and built so that all users are able to easily determine what they should or shouldn’t read.
However, there is also bad verbosity. Packing a lot of information into one element is bad verbosity. Users who navigate by that element are automatically presented with an overwhelming amount of information. There’s no easy way for them to determine what is important, or differentiate that element from others like it.
Conversely, rich semantics are good, because users can choose to investigate certain elements and skip over others. A sighted user likely takes it for granted that they able to direct their visual attention to focus on one thing and not another. Users of alternative interfaces should have the same rights and not be forced to read everything.
Problem: wrapping block link
The root cause of this bad verbosity was the anchor element (hyperlink) wrapping the entire card.
To understand why this approach was used at all, we need a quick history lesson.
The anchor element in HTML 4.01
Prior to HTML5, there were strict rules about how anchor (a
) elements could be used. In HTML 4.01, the a
element used to be an inline element.
An inline element differs from a block element as follows:
Content model
Generally, block-level elements may contain inline elements and other block-level elements. Generally, inline elements may contain only data and other inline elements. Inherent in this structural distinction is the idea that block elements create “larger” structures than inline elements.
HTML 4.01 Recommendation: Block-level and inline elements (published 1999)
This meant that in HTML 4.01 it was illegal to nest block-level elements like headings and paragraphs inside inline elements like links.
Consider this simple example of a block link:
The above markup would fail HTML 4.01 validation, with the following errors:
Line 11, Column 12: document type does not allow element “H2” here; missing one of “OBJECT”, “MAP”, “BUTTON” start-tag
Line 12, Column 11: document type does not allow element “P” here; missing one of “OBJECT”, “MAP”, “BUTTON” start-tag
The mentioned element is not allowed to appear in the context in which you’ve placed it; … One possible cause for this message is that you have attempted to put a block-level element (such as “
W3C HTML 4.01 Strict markup validator<p>
” or “<table>
“) inside an inline element (such as “<a>
“, “<span>
“, or “<font>
“).
Of course, designers didn’t (and still don’t) limit themselves to what is easily coded. So early Front End developers (like myself) sometimes worked around this limitation by using JavaScript. We would add a click listener to the parent container. When the container was mouse clicked, the inline child link would be fired. This approach provided mouse users with a large clickable hit area, while applying the actual link to the appropriate child element.
The anchor element in HTML5
Almost 10 years after HTML 4.01 was finalised, HTML5 hit the scene.
While it wouldn’t become an official W3C recommendation for another 6 years, developers were quick to embrace its new features.
One of these features was a small change to the anchor element:
One new and exciting thing you can do in HTML5 is wrap links round “block-level” elements.
Imagine you have a front page with lots of teasers for news articles, each of which leads to a page devoted to the full text of that article. Obviously, each story needs a heading and you’ll want an image too, and you’ll want them all clickable.
Note that you have three identical links …
In HTML 5, you code it like this … Now the single link surrounds the whole teaser, removing duplication and creating a much wider hit area to click.
HTML5 Doctor: “Block-level” links in HTML5 (published 2009)
The JavaScript workaround
With the advent of HTML 5, the JavaScript workaround fell out of fashion. Now it was encouraged to wrap links around everything. After some initial resistance, I accepted the new normal and updated my coding practices.
The anchor element in 2020
Fast-forward to 2020.
Developers are now encouraged to look to HTML5 for accessible solutions. This is seen as preferable to overuse of div
and span
elements, excessive JavaScript and misplaced WAI-ARIA.
And yet, the HTML 5.2 spec still supports the block link approach promoted in 2009:
The
HTML 5.2 Recommendation: Text-level semanticsa
element may be wrapped around entire paragraphs, lists, tables, and so forth, even entire sections, so long as there is no interactive content within (e.g., buttons or other links). This example shows how this can be used to make an entire advertising block into a link:
The much respected MDN web docs agree, stating that the link element may contain flow content. As mentioned in the content structure section of this post, flow content includes elements like our card title (h2
), description (p
) and tag list (ul
).
Content categories: Flow content, phrasing content, interactive content, palpable content.
Permitted content: Transparent, containing either flow content (excluding interactive content) or phrasing content.
MDN Web Docs: The Anchor element – Properties
Even WCAG 2.1 agrees:
Example 7
Using a link to wrap block level elements in HTML5.
WCAG 2.1 Techniques: Providing link text that describes the purpose of a link for anchor elements
Implications for linked images
As mentioned in my content structure rundown, accessible images require alt
attributes.
Usually the alt
attribute is used to describe the content of the image. This is so that someone with compromised vision can more clearly tell what they’re looking at, or understand how this image complements the surrounding content. The Web Accessibility Initiative (WAI) have produced a handy alt Decision Tree, which categorises this type of image as informative.
However, when an image is linked, the alt
attribute has a different purpose. In this case the image becomes functional and is either solely responsible for describing the link destination, or intentionally hidden to reduce verbosity.
This means that the wrapping block-link changes the image’s role from informative to functional. As there is additional content within my block-link, the image is redundant. A null value should be used for the alt
attribute (alt=""
) to intentionally hide the image.
This was why the reviewer had suggested removing the alt
attribute. It was another unintended side-effect of using a block-link.
Lesson: Always do manual accessibility testing
All of this means that while our block-linked card is technically valid, it is not particularly accessible to a real person.
This serves as a timely reminder that even established design patterns require manual testing for accessibility.
Part 4: Verbose card with tweaks
Now you understand why the HTML5 block link was considered a good idea and why it actually wasn’t.
Based on a poor review and damning evidence from VoiceOver and NVDA, you might logically assume that I leapt into action and recoded the link.
But I didn’t. You see, at this point, the design, build and integration were already done. It seemed like it was too late in the day to radically change the way that the card link was implemented.
So I did a quick brainstorm and tallied up some options to tweak the implementation.
Tweak option 1: follow the reviewer’s advice
The external accessibility reviewer had made some good suggestions:
Suggested that the graphics be treated as decorative. Determine whether the heading adds to the experience and code such that there is a pause between it and the following text.
External accessibility reviewer
I did not have a way to communicate with the reviewer directly, so I interpreted this as follows:
- Hide the functional (and redundant) image from screen readers by using a null value for the
alt
attribute (alt=""
) - Remove/reduce the nested semantic elements, using
div
andspan
elements to preserve the layout - Add visually hidden punctuation after the heading text, to create auditory separation between the pieces of content
I wasn’t enthusiastic about throwing away my precious semantics and replacing them with generic elements. So I implemented the null alt
attribute and went searching for a better solution to the broken structure. I would think about the punctuation and return to it later on.
Tweak option 2: aria-hidden
This option would involve hiding the verbose parts of the card from assistive technologies.
aria-hidden
could be added to the picture, publication date, description, tags heading and tag list. That would remove these elements from the Accessibility Tree, but not from the DOM. To explain why this was undesirable, I’ll explain the difference between the two.
The Document Object Model, or DOM, is what most users interact with. It’s a tall tree-like structure of HTML5 tags and attributes. You can view the DOM by opening your browser’s Developer Tools panel.
The Accessibility Tree is similar to the DOM, but focusses on semantics. The browser generates the Accessibility Tree from the DOM, for the benefit of assistive technologies like screen readers. Screen readers aren’t interested in the plethora of layout and JavaScript hooks in a typical page. But they do care about what those elements represent and what state the page is in at any given time.
If I removed the verbose elements from the Accessibility Tree using only aria-hidden
, they would still be in the DOM. I’d seen this approach in use in the wild, but it didn’t sit well with me.
The problem was that it would create an inconsistent user experience. Yes, it would work fine for non-sighted users, who would only be aware of whatever was in the Accessibility Tree. It would also work well for sighted users, who would only see what was in the DOM. But sighted users of screen readers would see the DOM version and hear the Accessibility Tree version. Now that could get confusing!
So for this reason, this option was a non-starter.
Tweak option 3: hidden punctuation
My main concern after my earlier manual accessibility testing was that both V0iceOver and NVDA had read my carefully crafted structure as one very long string of text.
Both screen readers had made a few pauses, but not in every location that I thought they should have. For example, in VoiceOver there was no pause between the “Verbose card link” title and the adjacent “Placeholder image” picture. Nor between the date and the description. Nor between the tags heading and list items. I’d assumed that the mere presence of the nested elements would cause a screen reader to pause at each one. It appeared that I was wrong.
The external accessibility reviewer had suggested that I could add a pause:
… code such that there is a pause between [the heading] and the following text
External accessibility reviewer
So, adding some additional punctuation seemed like a reasonable solution.
Identifying 7 ways to add hidden punctuation
I identified 6 ways that I could achieve this:
- adding visually hidden full-stop/period separators between elements
- adding visually hidden dash separators between elements
- adding
aria-label
to specific elements in order to prepend a text label - adding visually hidden text labels before specific elements
- adding a
title
to SVG elements which followed specific elements to append a text label - using CSS3 pseudo elements (generated content) as separators between elements
I also tested the existing markup as a baseline, to make a total of 7 ways.
Testing 7 ways to add hidden punctuation
I set about testing these approaches in 5 screen reader / browser combinations:
- macOS, using Safari with VoiceOver
- iOS, using Safari with VoiceOver
- Windows 10 Professional, using IE11 with NVDA
- Windows 10 Professional, using IE11 with JAWS
- Windows 10 Professional, using Edge with Narrator
The results were mixed, but I learned the following:
- A physical full-stop/period remains the preferred way to add a pause
- A dash is more noticeable when viewed in a screen reader’s visual links menu, but less effective when announced by the screen reader
- The
span
wrapping the hidden full stop likely increases its verbosity - Visually hidden text labels add context, but the screen reader does not pause beforehand so the text can appear to be part of the preceding element
- The SVG
title
adds context, but this is especially verbose in NVDA, and screen readers do not pause beforehand so the text can appear to be part of the preceding element - Some screen readers expose some of the HTML structure within the block link
- Screen readers provide more information about the HTML structure within the block-link when the link has focus, compared to when it is read from the link elements list
aria-label
replaces the contained text rather than supplementing it- pseudo elements are only exposed by Safari
The best way to add hidden punctuation
In my testing, I’d learned that a (visually hidden) full-stop/period was the most reliable way to add a pause.
I went back to the team with my findings:
While the block link is legitimate HTML5, its presence causes some of the tested screen readers to ignore the structural elements within the link. The addition of the requested pause(s) resolves this but adds extra verbosity in some screen readers, where these elements are treated individually. This is likely due to the wrapping markup which is necessary to hide the period from sighted users.
My recommendation to the development team
The actual screen reader results were:
- macOS, using Safari with VoiceOver – pauses at the period
- iOS, using Safari with VoiceOver – reads each element in the link separately: “Title, heading level 3, link”, “fullstop, heading level 3, link”
- Windows 10 Professional, using IE11 with NVDA – announces the period and the element type: “Title dot heading”
- Windows 10 Professional, using IE11 with JAWS – not tested
- Windows 10 Professional, using Edge with Narrator – pauses at the period
It wasn’t a perfect solution, but it was the best that I could come up with. I fully expected to get the green light to roll out some visually hidden full-stops.
Unfortunately, the development team saw things in a different way. They saw a less than perfect solution, requiring a lot of useless cruft in the template.
So after all that, this option was also a non-starter. However they did say that they would discuss this with the external accessibility reviewer.
Part 5: Succinct card
So there I was, slowly dying inside, knowing that I’d coded something that represented an accessibility barrier.

Then, one day, I stumbled a webinar in Tenon’s wonderful YouTube playlist. It was titled What makes an accessible card? I clicked play, secretly hoping that the presenter, Rian Rietveld, had the same definition of a card as I.
Thankfully she did.
Replacing the HTML5 block-link with an inline link
The pseudo-content trick
Rian had put together a pen demonstrating the HTML5 block link issue and several solutions. Titled Avoid multiple tabs to the same link, it demonstrated two potential solutions, taken from Janita Top and Heydon Pickering respectively.
Both solutions involved extending the size of a child link, so that it covered the parent element. This would allow the parent HTML5 block link to be removed and replaced with a child link on the heading element.
Rian didn’t explain the origins of Janita Top’s solution, but a link was provided to Heydon Pickering’s solution: the pseudo-content trick.
The pseudo-content trick used CSS3 generated content (pseudo elements) to inject an extra element within a child link. This element was then styled with CSS, to replicate the block-link experience for mouse users.
This would be great for accessibility, as only the heading text would appear when the link was inspected. This solution would be the opposite of verbose – it would be succinct.
The redundant click event
However, Heydon pointed out that the pseudo-content trick had one downside.
By stretching the link over the entire container, it prevented users from selecting the text within. This could be a problem, if for example users needed to copy and paste the text to the clipboard.
So he offered a second solution, the redundant click event. Imagine my surprise when I read this and realised that it was my old friend, The JavaScript workaround.
Code example
In this example, I’ve used the pseudo-content trick.
While Heydon’s redundant click event approach solves the text selection issue, I prefer to avoid relying on JavaScript for basic interactivity where possible. This means that there’s less risk of unintended side-effects and I have more sleep filled nights. So I left this limitation in.
Manual accessibility testing
Testing in Safari with VoiceOver, the reduction in link verbosity was obvious:
Link. Succinct card link.
VoiceOver’s rendition of the Succinct card link
I verified this using Internet Explorer 11 with NVDA:
Succinct card link.
NVDA’s rendition of the Succinct card link
Much better!
Additional tweaks
:focus-within
While the new card works much better for screen reader users, the experience is now a bit different for keyboard users.
When keyboard users tab between clickable objects, they’ll see a large blue rectangle when they focus (reach) the verbose link, but a small blue rectangle when they focus the heading link.
Thankfully there’s a quick CSS fix for this. The styling attached to the :focus-within
pseudo-class will be applied when a child element (or the element itself) has focus. Adding this pseudo-class to the card container ensures that keyboard users see a large blue rectangle when they focus the succinct linked card.
Part 6: Comparison of Verbose and Succinct cards
This comparison shows the two solutions side by side.
Code example
In the example below, the verbose and succinct card variations are wrapped in an unordered list.
Manual accessibility testing
Link. Verbose card link Placeholder image which reads four hundred ex three hundred. Saturday the fifth of September two thousand and twenty twenty An HTML five block link wraps the card’s contents. This creates verbose link output in screen readers. Tagged as tag one tag two tag three.
VoiceOver’s rendition of the Verbose card link
Link. Succinct card link.
VoiceOver’s rendition of the Succinct card link
Part 7: Summary
So there we are. After three attempts and much experimentation, I finally had a clean solution.
I’ll go back to my client and hopefully we can implement this change as part of some upcoming work.
Key learnings
In doing all of this, I learned some key things.
Firstly, it’s OK to be old school. Twenty years of development experience can actually be an asset rather than a curse. Modern techniques don’t always stand up to cross examination. When faced with a challenge, don’t be afraid to look in your rusty cobweb-lined toolbox for some inspiration.
Secondly, always do manual accessibility testing. Don’t rely on specifications, validators or vague notions of Best Practice. Challenge precedence. Always do due diligence and raise any concerns you have with your development team.
And don’t avoid doing manual accessibility testing just because you don’t have an eligible disability. Don’t think that because you’re not a power screen reader user, your feedback isn’t valid. People are introduced to assistive technologies at different points in their lives. You can safely and easily test high-level functionality like element lists. VoiceOver’s Rotor is a good starting point if you use a Mac.
Thirdly, use your brain. Don’t numbly implement advice from others without considering what you were trying to achieve and whether there is a better way to achieve it. There’s more than one way to do everything.
Further reading / viewing
If you’re interested in learning more about this topic, check out the following links from this post: