Keyframe
Keyframe · The log

Hover Animation in CSS: Effects That Feel Good Instead of Cheap

July 24, 2026 · Uncategorized

Hover is the smallest conversation a page has with a person. They move the cursor over something, the something responds, and in that half second they learn it is clickable. Get the response right and the whole interface feels considered. Get it wrong, too slow, too bouncy, too much, and it feels like a template someone downloaded.

Most good hover effects come down to one transition and one property that changes on :hover. The craft is in the details, so here are four that hold up.

The sliding underline

A link underline that grows in from the left beats the default browser underline in every way. It uses a pseudo-element scaled on the x axis.

.link {
  position: relative;
  text-decoration: none;
  color: #f4f4f5;
}

.link::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: -2px;
  width: 100%;
  height: 2px;
  background: #ff3d7f;
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.25s ease;
}

.link:hover::after { transform: scaleX(1); }

Scaling a transform costs the browser almost nothing, and setting transform-origin: left is what makes it unfurl from the start of the word rather than the middle. Flip it to right and it retreats the other way, a nice touch on a back link.

Lift and shadow on a card

Cards that rise slightly on hover feel physical, like you picked them up. Two properties do it: a small upward translate and a deeper shadow.

.card {
  transition: transform 0.2s ease, box-shadow 0.2s ease;
}

.card:hover {
  transform: translateY(-6px);
  box-shadow: 0 12px 30px rgba(0, 0, 0, 0.35);
}

Keep the lift small. Six pixels reads as a gentle rise, twenty pixels looks like the card panicking. The shadow growing alongside the movement sells the sense of height, because in real life things that come toward you cast a wider shadow.

Image zoom inside a frame

A photo that zooms slightly while its frame stays put is a staple of galleries and thumbnails. The frame clips the overflow, the image scales.

.frame {
  overflow: hidden;
  border-radius: 8px;
}

.frame img {
  display: block;
  transition: transform 0.4s ease;
}

.frame:hover img { transform: scale(1.06); }

The overflow: hidden on the frame is the part that matters. Without it the image spills past its box and the effect falls apart. A slower duration, closer to 0.4 seconds, suits a zoom because a big element moving fast looks nervous.

The touchscreen problem

Phones have no hover. A finger either taps or it does not, so any state you hide behind :hover is invisible to half your visitors, and worse, some browsers fire a sticky hover on tap that then refuses to clear. Never put anything essential, a menu, a button label, a link, behind hover alone.

The clean fix is to gate hover styles behind a media query that only fires for devices with a real pointer:

@media (hover: hover) {
  .card:hover { transform: translateY(-6px); }
}

Now the lift only happens where a cursor exists, and touch users get a clean tap with no ghost states. Hover effects are decoration on top of something that already works, never the thing itself. Buttons have their own version of this, covered in button animation, and the timing choices above come straight from the timing function guide. The pillar on CSS animation ties the set together.

← Back to the log