25 CSS Hover Effects With Clean Code for Each One
25 CSS hover effects with clean, copy-paste code for each one — from subtle underlines to full glassmorphism reveals. No fluff, just working examples.
Why CSS Hover Effects Still Matter in 2026
Hover effects are one of those things that seem trivial until you notice their absence. A flat, static UI with zero feedback just feels dead. Users click less. Bounce rates go up. It's not magic — it's physics. People expect the world to react when they touch it.
Honestly, the problem isn't that developers don't know hover effects exist. It's that most tutorials show you three examples, all with transition: all 0.3s ease, and call it a day. There are dozens of patterns worth knowing, and the difference between a 150ms ease-out and a 300ms cubic-bezier(0.25, 0.46, 0.45, 0.94) is the difference between polished and jank.
This article covers 25 effects, grouped by type, with clean minimal code for each. You can drop most of these directly into a project. Some work best paired with specific design styles — if you're building glassmorphism UIs, the blur reveal and frosted card effects near the end will hit differently alongside the glassmorphism components already in Empire UI.
Worth noting: all examples here use vanilla CSS. No libraries, no JavaScript. If you want pre-built components that already wire these up, browse the components — but if you want the underlying CSS knowledge, read on.
Text and Link Hover Effects (1–7)
Links get hovered more than anything else on a page. Yet most codebases still just toggle color with no transition. These seven patterns cover the range from dead-simple to genuinely impressive.
Effect 1 is the expanding underline — not text-decoration, but a ::after pseudo-element that scales from 0 to 100% on the X axis. It's clean, it's controllable, and you can set the color independently of the text.
/* Effect 1: Expanding underline */
.link-underline {
position: relative;
text-decoration: none;
color: inherit;
}
.link-underline::after {
content: '';
position: absolute;
bottom: -2px;
left: 0;
width: 100%;
height: 2px;
background: currentColor;
transform: scaleX(0);
transform-origin: right;
transition: transform 0.25s ease-out;
}
.link-underline:hover::after {
transform: scaleX(1);
transform-origin: left;
}Effects 2–4 are variations: center-out underline (change transform-origin to center), overline instead of underline (move to top: -2px), and a double-border that animates both simultaneously. Effect 5 is a letter-spacing push — letter-spacing: 0 to letter-spacing: 2px with overflow: hidden on the parent to prevent layout shift. Effect 6 is a color-fill background sweep using a gradient background-size trick. Effect 7 is a strike-through that draws across hovered text, useful for e-commerce price display.
That said, don't stack more than one of these on the same element. Pick the one that fits the context and commit to it.
Button Hover Effects (8–13)
Buttons are the highest-stakes hover target on any page. The wrong effect feels cheap; the right one adds real perceived quality. These six cover the patterns you'll actually reach for.
Effect 8 is the fill sweep — background slides in from left to right using a pseudo-element. Effect 9 is a border draw, starting at a single corner and tracing the full perimeter. Effect 10 is a shadow lift with a 4px upward translate, casting a deeper box-shadow on hover. Effect 11 is a magnetic nudge — subtle translate toward cursor direction (requires a couple lines of JS for the direction detection, but the visual is CSS). Effect 12 is a ripple using a ::before with border-radius: 50% that scales from 0 to 400%. Effect 13 is a shimmer pass, a diagonal gradient that sweeps across the button face once.
/* Effect 10: Shadow lift */
.btn-lift {
transform: translateY(0);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
transition:
transform 0.18s ease-out,
box-shadow 0.18s ease-out;
}
.btn-lift:hover {
transform: translateY(-4px);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.22);
}In practice, the shadow lift is the most universally applicable. It works on light backgrounds, dark backgrounds, glassmorphic cards, you name it. The 4px offset is the sweet spot — go to 8px and it reads as broken, go to 2px and nobody notices.
Card and Container Hover Effects (14–19)
Cards are where hover effects can really breathe. You've got more surface area, more layers to work with, and the user expects something richer than a color change.
Effect 14 is the tilt — a CSS perspective + rotateX/rotateY transform driven by mouse position (JS-calculated values, CSS does the interpolation). Effect 15 is a border glow: box-shadow: 0 0 0 1px transparent to box-shadow: 0 0 0 1px #6366f1, 0 0 20px rgba(99,102,241,0.3) — two shadow layers, one for the sharp border, one for the ambient glow. Effect 16 is content reveal: the card has a hover state where an inner overlay slides up from the bottom, exposing action buttons. Effect 17 is scale with context-aware blur — the hovered card scales to 1.03 while sibling cards blur slightly using :not(:hover) combinators.
/* Effect 17: Scale + sibling blur */
.card-grid:hover .card {
filter: blur(2px);
transform: scale(0.97);
transition: filter 0.2s, transform 0.2s;
}
.card-grid .card:hover {
filter: blur(0);
transform: scale(1.03);
z-index: 10;
}Effect 18 is the frosted glass card — backdrop-filter: blur(12px) sits at 0 opacity normally, transitioning to 1 on hover. This pairs naturally with the glassmorphism generator if you want to tune the exact blur and saturation values visually before coding them. Effect 19 is a gradient border using border-image or the border-via-background-clip trick, where the border animates its gradient angle on hover.
Quick aside: the sibling blur effect (17) is one of those things that looks complex in a Dribbble mockup but is maybe 8 lines of CSS. Once you see it working, you'll use it everywhere.
Image Hover Effects (20–22)
Images deserve their own category because the constraints are different. You're usually working within a fixed aspect ratio, and overflow: hidden is almost always involved.
Effect 20 is the zoom-in — transform: scale(1.08) on the img inside a clipped container. 300ms ease-in-out is the right timing here; faster reads as a jump, slower reads as sluggish. Effect 21 is a color overlay that fades in on hover: an absolutely-positioned ::after with a semi-transparent background and opacity: 0 normally. You can layer text inside this with a display: flex centering trick on the parent.
Effect 22 is a pan effect for landscape images — using object-position transition from 50% 50% to 100% 50%, you get a slow pan across the frame without JavaScript. Not every browser handled this smoothly before 2024, but it's solid now across all modern engines.
One more thing — all three of these stack cleanly. Zoom + overlay + pan together is a common pattern on portfolio sites, and the CSS stays under 20 lines.
Advanced and Specialty Hover Effects (23–25)
These last three are the ones you save for when you want to make an impression. They're still pure CSS but they require you to understand what's actually happening.
Effect 23 is the text clip gradient hover — the gradient exists on the parent, but it's only revealed on hover via background-clip: text and color: transparent transitioning in. It sounds simple written out, but the execution requires careful stacking context management so the gradient doesn't bleed into neighboring elements. Effect 24 is a clip-path wipe: the element starts with clip-path: inset(0 100% 0 0) and transitions to clip-path: inset(0 0% 0 0) — a clean directional wipe with no pseudo-elements needed. Works on buttons, images, and card overlays equally well.
/* Effect 24: Clip-path wipe reveal */
.wipe-label {
position: relative;
overflow: hidden;
}
.wipe-label .fill {
position: absolute;
inset: 0;
background: #6366f1;
clip-path: inset(0 100% 0 0);
transition: clip-path 0.3s cubic-bezier(0.4, 0, 0.2, 1);
z-index: -1;
}
.wipe-label:hover .fill {
clip-path: inset(0 0% 0 0);
}Effect 25 is the 3D flip card — two faces, backface-visibility: hidden, a perspective: 1000px on the wrapper, and rotateY(180deg) toggling on hover. It's a classic for a reason. The gotcha that trips people up: the back face needs rotateY(180deg) set in its base state, not just on hover. Get that wrong and both faces show simultaneously during the animation.
Look, if you're building a UI that mixes several of these effects together, it's worth checking out the full Empire UI component library — a lot of these patterns are already baked into the existing components rather than built from scratch each time. And if you're deep into design system territory, the box shadow generator will save you a ton of trial-and-error on effects 8, 10, and 15 specifically.
FAQ
For most interactions, 150–250ms feels snappy without being jarring. Anything over 400ms starts to feel like the UI is fighting you. Use ease-out for elements moving toward the user and ease-in for elements retreating.
Not reliably — touch events don't have a persistent hover state. On iOS, the first tap triggers hover and the second activates the click. Use @media (hover: hover) to scope your hover styles to devices that actually support it.
transition: all is a performance trap — it forces the browser to check every property on every frame. Always specify exactly what's changing: transition: transform 0.2s ease-out, opacity 0.2s ease-out.
Yes if they're the only affordance — don't hide critical content behind hover states with no keyboard equivalent. Pair hover effects with :focus-visible so keyboard users get the same feedback. Also respect prefers-reduced-motion for users who are sensitive to motion.