Block Links, Cards, Clickable Regions, Rows, Etc.

Whether you call them cards, block links, or some other thing, the construct of making an area of content clickable (tappable, Enter-key-able, voice-activatable, etc.) is not new.

While hit area size is mostly a usability issue, marketers often want a larger click area around their calls to action (CTAs) to make them more prominent. Without thinking about the impact on screen reader users, that can make for extremely verbose controls. For voice users, it can be confusing what to say to select one of these.

There are no earth-shattering techniques here. I am mostly demonstrating how doing it the wrong way can be a problem and how a little planning can make the better way less awful.

Perhaps the worst thing you can do for a block link is to wrap everything in the <a href>.

If you fail to make the link to display: block, it will have dead areas around words or chunks of text, which can make them feel like distinct links for a mouse user. If you use an underline on the entire block of text, it can be hard to read, while no underline at all can remove that visual affordance.

Worse, for a screen reader user the entire string is read when tabbing through controls. In the following example, the first link contains the heading, image (without declaring it as an image), and block of text, taking about 25 seconds to read before announcing it as a link. When tabbing, you do not always know the control type until the accessible name is complete.

Tabbing through the links in Firefox 72 with NVDA 2019.3.1.

The other two links wrap either the <h3> (second example) or the Read more… text (third example). For the third one I use aria-describedby to point to the <h3> for more context on the link, but that is not the only nor best way to do it.

The following example demonstrates arrowing through the content. Since there is a misconception among developers that screen reader users Tab through everything on a page, they often forget to test the primary way screen readers scan a page — by arrowing (or swiping if on a touch device).

Arrowing through the content in Firefox 72 with NVDA 2019.3.1.

You can hear how every chunk of text is announced as a link, and it is not clear that they are all the same link. Only a more skilled user is likely to recognize the pattern and guess they all go to the same place without further inspection.

The CSS for the block link is generally straightforward:

.block a[href]::after {
  content: "";
  display: block;
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
}

Note that this approach prevents selecting the text.

Heydon Pickering goes into more detail on cards in Inclusive Components, which generally covers the same stuff as here but without the screen reader examples.

Variations

I get asked about two challenges to this approach regularly. Content order and additional controls.

Content Order

Because I feel strongly that headings should be used to structure your content (content following a heading contextually belongs to that heading), some developers may not know how to mark up a design where an image visually appears above the heading. After all, if the image is in the card, it probably belongs to the heading and text in that card.

Side-by-side comparison of the same card with the image moved to the top in the second one.
The only difference between these two is visual. The image is moved up using CSS, but in the DOM still follows the heading.

This is one of the few cases where I advocate using CSS to re-order the content to achieve the visual design. If you use my reading order bookmarklet on this, you will see the image is moved above the heading.

The CSS:

.reorder {
  display: flex;
  flex-direction: column;
}

.reorder h3 {
  order: 2;
}

.reorder p {
  order: 3;
}

If you only set order: 1 on the <img> and none of the other content get an order defined, then the <img> will appear after the other content.

Content Order — Updated

As Dannie notes in the comments, I could have used the following CSS instead:

.reorder {
  display: flex;
  flex-direction: column;
}

.reorder img {
  order: -1;
}

I adjusted the Codepen example as well. I did not thoroughly test this, either. I cannot say if I just forgot about using a negative value or purged it from working memory due to some issue somewhere, so of course test.

Additional Controls

If you need to have an additional link, or a button, or some other control within the card and the design places it at the bottom or top (or even along a side), you can account for this by leaving space for it. This or similar CSS may do the trick:

.block.extracta a[href]::after {
  bottom: 3.75em;
}

.block.extracta p a[href]::after {
  content: none;
}

In my example, I am basing it on the the current text size (em units), but you may need to get more creative if your content will be unknown to you, translated, localized, or otherwise variable. My example is sloppy because I have such a broad selector for links in my cards, so I have to remove the generated content from the additional link.

Side-by-side comparison of the same card with the clickable area highlighted via browser dev tools.
The dev tools show the clickable area of the block link, leaving a gap at the bottom.

In the image, see that I leave some dead space above the other interactive control. Ideally this can help prevent mis-clicks/-taps.

Buttons

Most of what I review above applies to <button>s as well. As more and more web content tries to be more appy (think SPAs), a native button might be used to change settings or preferences. Dashboard pages often include card-like interfaces, and those cards similarly stuff way too much into a <button>.

Unlike a link, which allows nested headings, lists, and so on (regardless of how verbose that is), nesting structured content within a <button> removes that structure. An <h3> inside a <button> is no longer a heading. It is just a piece of text. The following video demonstrates that in action.

Tabbing through the buttons in Safari with VoiceOver on macOS.

Arrowing through the content of a <button> does not mean that structure will be exposed either.

Arrowing through the content in Safari with VoiceOver on macOS.

A user familiar with the screen probably is fine with a control that announces only as Settings. Even if confused, it is a simple (and faster) process to arrow backward or jump to the preceding heading for the needed context.

The styles I used to make block links generally apply here. If for some reason your button has an image or needs to have another control within its visual container, the tactics I used above also generally apply.

Demo

All the code for the examples is embedded below. You can visit the Codepen directly to play with the code, or view it in debug mode with your own assistive technology.

See the Pen vYOGpXb by Adrian Roselli (@aardrian) on CodePen.

Update: February 23, 2020

At the end of this post is a comment asking about Smashing Magazine’s home page. While I responded to the question, images do a better job.

A single article abstract on the Smashing Magazine home page. A single article abstract on the Smashing Magazine home page showing all seven links.. A single article abstract on the Smashing Magazine home page showing the article link overlapping all 7 links plus a contrast and scroll-to-top link.
The first image is a single article abstract from the home page. The second image shows the seven (7) distinct links in that card layout (there can be a minimum of 6, and the number climbs with each category). The third image shows the effects of a block link using ::before that overlaps all seven links as well as the page’s contrast widget and scroll-to-top feature.

In my response, I note that the simplest fix for this problem is to remove the ::before on the <a> around the article title. This would help average mouse and touch users. It can also be a boon to mobility-impaired users and blind users who may be using explore-by-touch in their mobile screen reader.

Update: March 31, 2020

I was asked about clickable table rows, which are a particular annoyance to many screen reader users. The current practice across libraries is to attach a click handler to the row, which means everything in the row announces as clickable.

We can adapt the block approach to work with rows. Unlike cards, you want to the control’s clickable area to extend beyond its parent container, which is a cell.

In the following example, the width is generally known (100% minus the 1vw margins) and the tables are rather wide. While not perfect (on wide screens) this at least works for the example and shows the concept.

There are two tables. The first uses links in the first row, and the second example uses buttons in the last row.

Visit the tables example directly at Codepen or view the tables example in debug mode and test with your own assistive technology.

See the Pen Block Table Rows by Adrian Roselli (@aardrian) on CodePen.

Update: April 1, 2020

Great point about selecting text from cells:

I mention the copying caveat elsewhere, but specifically on this point:

As always, context matters, test with your users.

11 Comments

Reply

Great post as always, Adrian!

Just a small note on the reordering:

You can reorder with negative numbers meaning that you only need to set the order of the img to “-1”, effectively generating the same result. That way, you won’t have to keep track of the ordering for the other elements.

img {
  order: -1;
}
Dannie; . Permalink
In response to Dannie. Reply

Thanks! I have adjusted the example code and pen.

Reply

What would your recommendation be for cards that contain ‘nested’ links? For example the links to articles on Smashing Mag where the card also contains a link to the author.

David; . Permalink
In response to David. Reply

Yeah, as your quotes imply, they aren’t nested, but the clickable areas clearly overlap.

Those links are a bit much. The article title clickable area overlaps all the other links (author, comments, and tags), so a user needs precision to avoid a mis-click/-tap. For the top-most links, the clickable area overlaps the <h1> and the contrast switcher button. Similarly, the hit area butts up against adjacent article hit areas.

My post Target Size and 2.5.5 talks about the need for dead space between clickable / tappable areas, showing examples from platform development guidelines.

The two links to the author have the same link text (the alt is the same as the text), so those should be combined.

To wrap this up, the link to each article should be generally limited to the article title. The simplest fix would to remove the ::before on the article title <a>.

In response to David. Reply

David, I updated the post with images that might better explain what I was saying.

Reply

Great post, thanks for the detailed writeup. I often use aria-owns to reorder the DOM and it’s pretty well supported. It might be worth trying that instead of relying on CSS to re-order the DOM?

Gurmukh Panesar; . Permalink
In response to Gurmukh Panesar. Reply

Gurmukh, I am not re-ordering the DOM in CSS. I am using CSS to visually adjust the layout.

Given that a screen reader is needed to understand that relationship, relying on this approach can leave other AT users (such as keyboard-only users) caught out if the layout becomes more complex or starts to integrate other interactive components.

Where aria-owns is supported, that may be adequate for your use case. I avoid using ARIA unless I have a compelling case.

Reply

Really enjoyed this post. One consideration I found with the third example (Somewhere in text) is that the Read more… link’s `aria-describedby` does not appear in Rotor mode in VoiceOver, so the context of the heading is lost. If a site had several cards and users were navigating via a list of links in Rotor mode, they wouldn’t be able to differentiate between distinct links. What about including the heading text directly within the link as screen-read-only text?

This is a tricky pattern! I’ve been following the pseudo element approach but I’m sensitive to the fact that users can’t select text.

In response to David Luhr. Reply

David, because aria-describedby is supplemental information, VO is behaving as expected. It’s the same in NVDA and JAWS when using their links navigation dialogs (analogous to the rotor). When moving to the link, NVDA will at least announce the aria-describedby, but JAWS does not under default settings.

If this is a problem for your users, you can use aria-labeledby to construct a more detailed link or use visually-hidden text. Your approach will depend on needs, verbosity, expectations, AT support, and good old-fashioned user testing.

Reply

Silly question time:
You say “When tabbing, you do not always know the control type until the accessible name is complete.”
But why don’t screen readers announce a link BEFORE reading the link? To me it’s always sounded counterintuitive to announce the link after reading it.

Jonathan Douglas Holden; . Permalink
In response to Jonathan Douglas Holden. Reply

It depends how you are navigating and/or which screen reader. Listen to the first two videos. In NVDA, when tabbing, “link” is read after the accessible name; when arrowing, it is read before. In VoiceOver in the second two videos, “button” is announced after no matter how you navigate. These are decisions the screen readers have made which are ostensibly based on testing with their own users (or getting feedback from them).

Leave a Comment or Response

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>