Rethinking Hover States
Why most hover effects feel cheap, and how to design interactions that communicate hierarchy and affordance.
Hover states are the handshake between a user and an interface. They communicate "this is interactive" before any click happens. Yet most hover effects are an afterthought — a color swap, maybe an underline.
The problem with opacity
The most common hover pattern is reducing opacity:
.link:hover { opacity: 0.7; }
It works, but it's lazy. Opacity changes feel like the element is fading out, not inviting interaction. The user's instinct is "this is disappearing" rather than "this is responding to me."
Feel the difference yourself — hover each card:
Better alternatives
1. Subtle translate
A small upward shift (1–2px) signals responsiveness without visual noise:
.card {
transition: transform 200ms cubic-bezier(0.4, 0, 0.2, 1);
}
.card:hover {
transform: translateY(-2px);
}
2. Background reveal
Show a subtle background on hover to define the interactive area — the same pattern this site uses on work list items.
.item {
padding: 8px 12px;
border-radius: 6px;
transition: background-color 150ms ease;
}
.item:hover {
background-color: var(--bg-surface);
}
3. Underline animation
For text links, animate the underline from left to right:
Hover or focus — the underline grows from left to right
.link {
background-image: linear-gradient(currentColor, currentColor);
background-size: 0% 1px;
background-position: left bottom;
background-repeat: no-repeat;
transition: background-size 300ms ease;
}
.link:hover,
.link:focus-visible {
background-size: 100% 1px;
}
Match the nature of the action
Every hover state should answer: what happens next? If clicking will navigate, the hover should suggest movement. If clicking will expand, the hover should suggest growth.
Match the interaction's nature, not just its existence.