Design Systems Hot Takes

Clickable cards

5 min read

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:

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. 😉

Storybook iframe tango