Block Links: The Search for a Perfect Solution
I was reading this article by Chris where he talks about block links — you know, like wrapping an entire card element inside an anchor — being a bad idea. It’s bad accessibility because of how it affects screen readers. And it’s bad UX because it prevents simple user tasks, like selecting text. But maybe there’s something else at play. Maybe it’s less an issue with the pattern than the implementation of it. That led me to believe that this is the time to write follow-up article to see if we can address some of the problems Chris pointed out. Throughout this post, I’ll use the term “card” to describe a component using the block link pattern. Here’s what we mean by that.
- The whole thing should be linked and clickable.
- It should be able to contain more than one link.
- Content should be semantic so assistive tech can understand it.
- The text should be selectable, like regular links.
- Things like right-click and keyboard shortcuts should work with it
- Its elements should be focusable when tabbing.
That’s a long list! And since we don’t have any standard card widget provided by the browser, we don’t have any standard guidelines to build it.
Like most things on the web, there’s more than one way to make a card component. However, I haven’t found something that checks all the requirements we just covered. In this article, we will try to hit all of them. That’s what we’re going to do now!
Method 1: Wrap everything an
This is the most common and the easiest way to make a linked card. Take the HTML for the card and wrap the entire thing in an anchor tag.
Well, not great. We still can’t:
- Put another link inside the card because the entire thing is a single link
- Use it with a screen reader — the content is not semantic, so assistive technology will announce everything inside the card, starting from the time stamp
- Select text
That’s enough 👎 that we probably shouldn’t use it. Let’s move onto the next technique.
Method 2: Just link what needs linking
This is a nice compromise that sacrifices a little UX for improved accessibility.
With this pattern we achieve most of our goals:
- We can put as many links as we want.
- Content is semantic.
- We can select the text from Card.
- Right Click and keyboard shortcuts work.
- The focus is in proper order when tabbing.
But it is missing the main feature we want in a card: the whole thing should be clickable! Looks like we need to try some other way.
Method 3: The good ol’ ::before pseudo element
In this one, we add a : :before or : :after element, place it above the card with absolute positioning and stretch it over the entire width and height of the card so it’s clickable.
- We still can’t add more than one link because anything else that’s linked is under the pseudo element layer. We can try to put all the text above the pseudo element, but card link itself won’t work when clicking on top of the text.
- We still can’t select the text. Again, we could swap layers, but then we’re back to the clickable link issue all over again.
Let’s try to actually check all the boxes here in our final technique.
Method 4: Sprinkle JavaScript on the second method
Let’s build off the second method. Recall that’s what where we link up everything we want to be a link:
A Complete Guide to calc() in CSS
In this guide, let’s cover just about everything there is to know about this very useful function.
Chris Coyier calc
So how do we make the whole card clickable? We could use JavaScript as a progressive enhancement to do that. We’ll start by adding a click event listener to the card and trigger the click on the main link when it is triggered.
const card = document.querySelector(".card") const mainLink = document.querySelector('.main-link') card.addEventListener("click", handleClick) function handleClick(event)
Temporarily, this introduces the problem that we can’t select the text, which we’ve been trying to fix this whole time. Here’s the trick: we’ll use the relatively less-known web API window.getSelection . From MDN:
The Window.getSelection() method returns a Selection object representing the range of text selected by the user or the current position of the caret.
Although, this method returns an Object, we can convert it to a string with toString() .
const isTextSelected = window.getSelection().toString()
With one line and no complicated kung-fu tricks with event listeners, we know if the user has selected text. Let’s use that in our handleClick function.
const card = document.querySelector(".card") const mainLink = document.querySelector('.main-link') card.addEventListener("click", handleClick) function handleClick(event) < const isTextSelected = window.getSelection().toString(); if (!isTextSelected) < mainLink.click(); >>
This way, the main link can be clicked when no text selected, and all it took was a few lines of JavaScript. This satisfies our requirements:
- The whole thing is linked and clickable.
- It is able to contain more than one link.
- This content is semantic so assistive tech can understand it.
- The text should be selectable, like regular links.
- Things like right-click and keyboard shortcuts should work with it
- Its elements should be focusable when tabbing.
We have satisfied all the requirements but there are still some gotchas, like double event triggering on clickable elements like links and buttons in the card. We can fix this by adding a click event listener on all of them and stopping the propagation of event.
// You might want to add common class like 'clickable' on all elements and use that for the query selector. const clickableElements = Array.from(card.querySelectorAll("a")); clickableElements.forEach((ele) => ele.addEventListener("click", (e) => e.stopPropagation()) );
Here’s the final demo with all the JavaScript code we have added:
I think we’ve done it! Now you know how to make a perfect clickable card component.
What about other patterns? For example, what if the card contains the excerpt of a blog post followed by a “Read More’ link? Where should that go? Does that become the “main” link? What about image?
For those questions and more, here’s some further reading on the topic:
- Cards by Heydon Pickering
- Block Links, Cards, Clickable Regions, Rows, Etc. by Adrian Roselli
- Block Links Are a Pain (and Maybe Just a Bad Idea) by Chris Coyier
- Pitfalls of Card UIs by Dave Rupert
Wrapping Long URLs and Text with CSS
By default, the white-space property is set to normal . So you might see something like this when trying to force long URL s and other continuous strings of text to wrap:
To force long, continuous strings of text to wrap within the width of our content (or other block-level element, such as and
), we need a different value for the white-space property. Here are our options:
- normal – Default value for the white-space property. Sequences of whitespace are collapsed to a single whitespace. content will wrap at whitespaces according to the width of the element.
- nowrap – Sequences of whitespace are collapsed to a single whitespace. content will wrap to the next line ONLY at explicit
elements. - pre – All whitespace is preserved. content will wrap at implicit line breaks. This is the default behavior of the element.
- pre-line – Sequences of whitespace are collapsed to a single whitespace. content will wrap at whitespaces and line breaks according to the width of the element.
- pre-wrap – All whitespace is preserved. content will wrap at whitespaces and line breaks according to the width of the element.
- inherit – Value of white-space inherited from parent element.
In a perfect world, we could simply use white-space:pre-line , like so:
Although the white-space property is supported by all major browsers, unfortunately many of them fail to apply the property to long strings of continuous text. Different browsers will wrap long strings, but they require different white-space values in order to work. Fortunately, we can apply the required values for each browser by including multiple white-space declarations in our pre selector. This is exactly what we are doing with the CSS code solution presented at the beginning of this article.
The comments included in the CSS solution explain which declarations are targeting which browsers. Notice that some of the rules are browser-specific (using vendor-specific prefixes), while others declare standard values from different CSS specifications. The funky word-wrap property is a proprietary Microsoft invention that has been included with CSS 3. And thanks to the CSS forward compatibility guidelines, it’s perfectly fine to include multiple instances of the same property. In a nutshell:
- Unrecognized properties are ignored
- For multiple instances of the same property, only the last rule will be applied
The code solution presented in this article seems to work fine in every browser I have tested, but it doesn’t validate because of the vendor-specific stuff and the crazy Microsoft thing.
For more complete discussion on text wrapping and all the gruesome details, check out Lim Chee Aun’s excellent post on whitespace and generated content ( 404 link removed 2014/03/24). And, for a complete guide to pimping your site’s content, check out my article, Perfect Pre Tags.
Wrap Long Links with CSS
Developers have loads to think about when creative websites, and much of that is ensuring child elements don’t stretch past the parent width. We worry about images, IFRAMEs, and other popular elements pushing past their parent width, but then we see a basic link with a long URL and we look down, just shaking our heads. Why doesn’t the URL just break?
To prevent that issue, you can apply the following CSS:
Should this be a global setting for A elements, or simply something that we as developers should set? Surely this is an annoyance we shouldn’t have to deal with, right?
Recent Features
Vibration API
7 Essential JavaScript Functions
Incredible Demos
Page Peel Effect Using MooTools
Dynamically Load Stylesheets Using MooTools 1.2
Discussion
You have to be carefull when using it in tables, because it causes the table to split the links all the time, it’s annoying. I prefer to set word-wrap on each case, most of time reseting this, is worst than setting.
Chris, that would be true if you set the CSS style on the body / p element. This will only effect links, hence the setting of it on the a element.
I commonly run into this when dealing with dynamic content. I feel it should be automatically set for the anchor tag. We could just implement this in a reset included in the stylesheet that _most_ projects start off with. Double-edge sword I guess.
Actually, I do exactly the opposite. I think you shouldn’t wrap links like that at all. Imagine if you had a link at the end of a sentence that breaks to the next line. Now, you actually have two links. One to the right of your screen, and one to the left. But they are the same link. Confusing and ugly, in my opinion. Better is to white-space: nowrap all the links in a content section, and provide a max-width (which could be dynamic for various screen sizes). And then probably make it visually attractive by letting it fade out or add ellipsis. Like @Symphony shows in the above comment. Breaking a link in two parts feels kinda like breaking a word with a hyphen on a place where it isn’t logical/allowed.
Agree with @Jelmer. A progressive reveal pattern seems most useful. For ease of implementation, we should look not at the tag but using CSS ellip for style and the title attribute as a semantic way to provide additional information for a basic tooltip. Similarly, we could lookup the site title dynamically and embed it where the really-really-long-a-tag-body-text-node-goes, passing more link juice through on the site title, assuming required use of the A href attribute and the link is not a nofollow link.
Consider my case on mobile devices, from where I get 80% of my traffic, I want the client to see the entire link and judge if it’s safe or not. Since screen width is reduced on mobile, breaking the link on two lines doesn’t leave one right of the screen and other to the left, but actually makes it easier to be clicked. This is just for my case, ellipsis doesn’t suit everywhere.