Reading time: 16 minutes


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:

  1. Part 1: What is a card, in which I describe the card design pattern
  2. Part 2: Anatomy of a card, which dissects the HTML structure of a card
  3. Part 3: Verbose card, where I discuss a coded example of an inaccessible card
  4. Part 4: Verbose card with tweaks, where I make several attempts to improve the accessibility of the verbose card, without changing its basic structure
  5. Part 5: Succinct card, where I discuss a coded example of a more accessible card
  6. Part 6: Comparison of Verbose and Succinct cards, where I briefly compare the two cards
  7. 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.

Back to Table of contents

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:

  1. Title (h2)
  2. Picture (img)
  3. Publication date (p)
  4. Description (p)
  5. Category tags subtitle (h3)
  6. 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:

  1. Title (order:2)
  2. Picture (order:0)
  3. Publication date (order:1)
  4. Description (order:2)
  5. Category tags subtitle (order:2)
  6. 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.

Back to Table of contents

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)

  1. Select System Preferences: Accessibility: Preferences: VoiceOver: Enable VoiceOver
  2. Press CONTROL + OPTION + U, to open Rotor
  3. Press RIGHT ARROW, until you hear “Links Menu”
  4. Press UP ARROW or DOWN ARROW, to read each link in the list

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)

  1. Press NVDA key (e.g. CAPSLOCK) + F7, to open the Elements List
  2. Press ALT + K to check the “Links” radio button
  3. Press TAB to focus the tree view
  4. Press UP ARROW or DOWN ARROW until the desired link has focus

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 “<p>” or “<table>“) inside an inline element (such as “<a>“, “<span>“, or “<font>“).

W3C HTML 4.01 Strict markup validator

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 a 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:

HTML 5.2 Recommendation: Text-level semantics

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.

Back to Table of contents

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:

  1. Hide the functional (and redundant) image from screen readers by using a null value for the alt attribute (alt="")
  2. Remove/reduce the nested semantic elements, using div and span elements to preserve the layout
  3. 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:

  1. adding visually hidden full-stop/period separators between elements
  2. adding visually hidden dash separators between elements
  3. adding aria-label to specific elements in order to prepend a text label
  4. adding visually hidden text labels before specific elements
  5. adding a title to SVG elements which followed specific elements to append a text label
  6. 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:

  1. macOS, using Safari with VoiceOver
  2. iOS, using Safari with VoiceOver
  3. Windows 10 Professional, using IE11 with NVDA
  4. Windows 10 Professional, using IE11 with JAWS
  5. 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:

  1. macOS, using Safari with VoiceOver – pauses at the period
  2. iOS, using Safari with VoiceOver – reads each element in the link separately: “Title, heading level 3, link”, “fullstop, heading level 3, link”
  3. Windows 10 Professional, using IE11 with NVDA – announces the period and the element type: “Title dot heading”
  4. Windows 10 Professional, using IE11 with JAWS – not tested
  5. 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.

Back to Table of contents

Part 5: Succinct card

So there I was, slowly dying inside, knowing that I’d coded something that represented an accessibility barrier.

Frowning cartoon face

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


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.

Back to Table of contents

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

Back to Table of contents

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:

Back to Table of contents

Leave a comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.