Huge links
We often need to make entire elements clickable, for instance cards or table rows. There are a lot of ways to do it, but some of them are better than others in terms of accessibility.
Let’s consider that we want to make the following card clickable:
<Card>
<img src="hotel.jpg" alt="Nice hotel" />
<a href="/book">Book now!</a>
</Card>
I’m using a Svelte/React/Vue-like syntax here, but we’d write it <div class="card">
if we were using Bootstrap.
Right now, while the link is clickable, its hitbox is way too small. We want to make the whole card clickable. To achieve this effect, many solutions are possible:
- Make the element clickable with JavaScript.
- Wrap the whole element in a
<a>
tag. - Use pseudo-elements to make the link hitbox bigger.
Using pseudo-elements
Pseudo-elements are a powerful CSS feature: they allow creating HTML-like elements out of nothing. These elements can then be positioned and interactive, like all other elements.
We’ll create a pseudo element on our card link, and since it’ll be a child of the link, it’ll be clickable too.
.card a::before {
content: "Hey!";
}
That works great, the pseudo-element behaves like it is a direct text-node of the link. The only thing that betrays its nature is the fact that it cannot be selected.
Now, let’s try positioning it.
.card {
position: relative;
}
.card a::before {
position: absolute;
top: 0;
left: 0;
content: "Hey!";
outline: 2px solid white;
}
I added position: relative
to .card
to make it the link’s positioning ancestor. Indeed, when using position: absolute
or fixed
, the element you’re positioning is placed, not relative to the page, but relative to its closest positioned ancestor. If I hadn’t positioned the card, the element would be in the top left corner of the page.
When hovered, the pseudo-element triggers its parent animation, and it can be clicked too.
Filling the whole space
There is a shorthand property that means the same as top/bottom/left/right: 0
: it’s inset: 0
. This leads to the following code:
.card {
position: relative;
}
.card a::before {
content: "";
position: absolute;
inset: 0;
}
Our card is now fully-clickable, and the link animation triggers when the card is hovered.