While having a good time with internet friends on Bluesky, I saw a thread asking about Josh Comeau’s website, specifically asking about why the entire card isn’t clickable. Josh’s replies concerning usability is spot on, and I’ve definitely been naughty doing the typical display: block
on a <a/>
element right here on the blog homepage. The replies in the thread also have some good suggestions but I was wondering if there was alternative. I have two main criteria:
- Use HTML & CSS only, so there’s no need to listen for additional behavior.
- Ensure that it meets user expectations both in terms of accessibility and also visually.
I think I have something…
Forgotten memories
In the list of HTML tags that people forget about, there’s two that are very often unused: <map/>
and <area/>
. These are meant to be used with an associated <img/>
so a user can click defined regions of an image. The example for <area/>
on MDN has a good sample of this.
The idea is that you define the areas’ shapes using some attributes and then group them into the <map/>
. Then you assign that to the <img/>
using usemap
, which is a reference to the <map/>
. Each <area/>
can accept many of the same attributes of an <a/>
, most importantly including href
and target
.
So what if we could include a visually hidden image the size of the card and then add an href
to the entire area of the image? Let’s take a look:
Sourcery
We certainly don’t want to make a request for an image that is only being used to support this behavior. We’d want some resource that was quick and hard-coded. Stack Overflow has a topic on “smallest filesize for transparent single pixel image” which is exactly what we need. I’ll add a few additional attributes as well.
<img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEAAAAALAAAAAABAAEAAAIBAAA="
aria-hidden="true"
height="100%"
width="100%"
usemap="#name"/>
The aria-hidden="true"
is added because this image is strictly to support the area that we want to click, we don’t want to expose it in the accessibility tree. The width
and height
attributes are ensuring that the image is the full size of the container it is placed within. And the usemap="#name"
is meant to reference a <map name="name"/>
somewhere in the page.
Next we’ll make the <map/>
and <area/>
:
<map name="card">
<area
alt="Design Systems Hot Takes"
href="https://blog.damato.design"
shape="default"
target="_blank"/>
</map>
The href
and target
should be clear. The shape="default"
tells the <area/>
to be the same size as the <img/>
. The alt
is the same text you’d expect for images, except here we’re using it as the label for the link. On MDN, it mentions that this is used for browsers that don’t show images.
Finally, let’s put all of this in a card:
<div class="card">
<div class=card-contents">
Card contents
</div>
<map name="card">
<area
alt="Design Systems Hot Takes"
href="https://blog.damato.design"
shape="default"
target="_blank"/>
</map>
<img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEAAAAALAAAAAABAAEAAAIBAAA="
aria-hidden="true"
height="100%"
width="100%"
usemap="#name"/>
</div>
Styles
Now for some styles, and there isn’t a lot so I’m going to write everything below and explain:
.card {
display: grid;
}
.card:focus-within {
outline: 5px auto Highlight;
outline: 5px auto -webkit-focus-ring-color;
}
.card > :not(map) {
grid-area: 1 / 1 / -1 / -1;
}
.card > map {
position: absolute;
transform: scale(0);
}
First, the grid styles are a different way of getting the direct children to fill the container. Traditionally we’d place position: relative;
on the .card
and then position: absolute;
on the img
. However, that could cause layering problems since position
creates a new stacking context. The grid properties here will make the children fill the container.
Next, the .card:focus-within
listens for when the area
element is focused and applies a ring to identify this card is ready to be interacted with. You can add your own indication, I’m using the default browser styles.
Finally, the styles for map
ensure that it doesn’t take up any space in the composition but is still reachable with assistive technologies. The styles within here are a new way of supporting visually hidden so the alt
text is reachable.
I have a CodePen of the implementation below, including the traditional block anchor:
Results
So now this should allow for all the normal link behavior; hovering to see the link destination, right-click to open in new window, etc.. This also allows for some more interesting options. Perhaps you’d want to allow for parts of the card to be clickable so that other text can be selectable, you could adjust the position of the <img/>
to cover the area. Maybe you’d want to conditionally allow text in the card to be selectable. Use CSS to hide the <img/>
.
Have I tested anything else past what I’ve said here? Nope. But, maybe someone can be further inspired and continue down this path.
Is this really any different from adding a display block <a/>
and filling it inside the card like it is done with the <img/>
? Sort of, since it would still need content for assistive technologies to read, and you’d need to wrap the content in a <span/>
and visually hide it specifically for ATs, or maybe use an aria-label
. Maybe it’s easier, but maybe there’s something to the approach above that makes it better in some way?
At the very least, I bet you probably learned about some HTML elements you haven’t heard of. 😉