GODRICH
#css-animation#button#loader#transition#scroll#dashboard#ui#design-trends

9 CSS Card Hover Effects — One Line, Copy-Paste

These nine css card hover effects each act on a whole card component at once, not a single button inside it, using nothing heavier than transform, opacity, and

Auto-plays · click a tile to jump to its section · all nine in one zip

The order below follows how a card actually gets used: 01 and 02 give a card presence before any pointer arrives, and 03 through 05 add depth or reveal extra content once one lands. 06 through 08 nudge toward the next click, and 09 confirms that click actually registered. Watch the grid above instead of trying to click it — each thumbnail plays inside a fixed iframe that a real pointer can never reach. So every card loops through its hover state automatically, through the same is-demo class the standalone demos below rely on. Every effect also mirrors its :hover state onto :focus-visible and a matching @media (hover: none) block, so keyboard and touch visitors land on the same finished look a mouse would trigger. Two of the nine — 03 and 08 — additionally read the pointer's live position through a short JavaScript listener; the rest are simple card hover effects css handles alone once a card gets hovered, focused, or pressed. For a card that holds a conversation rather than a product, the CSS chat bubble animations set picks up where 04's slide-up stops, and item 07's gradient ring has a type-side counterpart in the CSS text shine animation demos.

01Lift with shadow

How it works

The whole card lifts 8px on hover, focus, and press alike while its box-shadow deepens from a pressed default into a raised one, sharing a single transition so neither property finishes ahead of the other.

Where it fits

It suits a grid of product or article cards, where the raised card needs to stand out from its neighbors before anyone commits to a click.

Lift with shadow
Open in a new tab

01 · Lift with shadow — Measured area 11.669% · moving frames 17/23Original by GODRICH · SCSS and React ship in the zip · See the password

transform: translateYbox-shadow 확대transition
.card {
  transform: translateY(0);
  transition: transform $duration $easing, box-shadow $duration $easing;
}
.card:hover,
.card:focus-visible,
.card:active {
  transform: translateY(-8px);
  box-shadow: $shadow-raised;
}

02Spinning border light

How it works

A conic-gradient sliver of light starts spinning along the card's outline the moment hover — or the idle grid loop — fades its wrapper's opacity in, rather than turning underneath a hidden layer the whole time.

Where it fits

The rotation itself comes from animating a registered @property angle rather than nudging a background position, so the light sweeps smoothly instead of jumping between fixed stops.

Spinning border light
Open in a new tab

02 · Spinning border light — Measured area 1.778% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password

conic-gradient@property angleopacity
@property --angle {
  syntax: "<angle>";
  inherits: false;
  initial-value: 0deg;
}
.card__ring {
  background: conic-gradient(from var(--angle), transparent 0deg, $color 40deg, transparent 100deg);
  opacity: 0;
}
.card:hover .card__ring,
.card:focus-visible .card__ring {
  opacity: 1;
  animation: spin $duration linear infinite;
}
@keyframes spin { to { --angle: 360deg; } }

033D tilt parallax

How the css 3d card hover effect reads the pointer

Eight lines of JavaScript turn the pointer's position inside the card into three CSS custom properties, and the card's rotateX/rotateY read those variables live. A background layer sits behind it on a fixed translateZ offset and shifts the opposite way through its own translateX value.

Where it fits

Tabbing to the card or pressing it without a mouse falls back to one fixed tilt angle instead of tracking anything, so the sense of depth still shows up without a pointer in the room.

3D tilt parallax
Open in a new tab

03 · 3D tilt parallax — Measured area 11.272% · moving frames 18/23Original by GODRICH · SCSS and React ship in the zip · See the password

perspectiverotateX/rotateYJS 좌표
card.addEventListener('pointermove', function (e) {
  var r = card.getBoundingClientRect();
  var px = (e.clientX - r.left) / r.width - .5;
  var py = (e.clientY - r.top) / r.height - .5;
  card.style.setProperty('--rx', (-py * 14) + 'deg');
  card.style.setProperty('--ry', (px * 16) + 'deg');
  card.style.setProperty('--pz', (px * -12) + 'px');
});

04Content slides up

How it works

A caption panel sits pushed down to 70% of its own height under the card's media. Hover, focus, or the idle loop slides it fully into view while its meta line fades in over the same span.

Where it fits

The card's own height never changes, since the panel is positioned absolutely rather than pushing the layout around it.

Content slides up
Open in a new tab

04 · Content slides up — Measured area 11.14% · moving frames 18/23Original by GODRICH · SCSS and React ship in the zip · See the password

transform: translateYoverflow: hiddenopacity
.card__panel {
  transform: translateY(70%);
  transition: transform $duration $easing;
}
.card:hover .card__panel,
.card:focus-visible .card__panel {
  transform: translateY(0);
}

05Background zoom

How it works

The full card background scales from 1 to 1.12 on hover or focus, and overflow: hidden on both the card and the background layer keeps the zoomed edge from ever crossing the card's rounded corners.

Where it fits

A sun shape and a hill shape sit on top of the gradient specifically so the zoom has hard edges to move against — the reason matters, and it's covered in the trap below.

Background image zoom
Open in a new tab

05 · Background image zoom — Measured area 7.676% · moving frames 20/23Original by GODRICH · SCSS and React ship in the zip · See the password

transform: scaleoverflow: hiddenborder-radius 유지
.card { overflow: hidden; }
.card__bg {
  transform: scale(1);
  transition: transform $duration $easing;
}
.card:hover .card__bg,
.card:focus-visible .card__bg {
  transform: scale(1.12);
}

06Icon bounce

How it works

An arrow badge in the card's corner plays a two-cycle translateY bounce the instant hover, focus, or a press begins. It uses a springy cubic-bezier easing instead of a linear one, so each bounce feels elastic rather than mechanical.

Where it fits

The grid preview runs a separate, endlessly looping version of the same keyframes so it has motion to show without a pointer ever landing on it.

Icon bounce
Open in a new tab

06 · Icon bounce — Measured area 2.056% · moving frames 18/23Original by GODRICH · SCSS and React ship in the zip · See the password

@keyframes bouncecubic-beziertransform: translateY
.card__icon { transform: translateY(0); }
.card:hover .card__icon,
.card:focus-visible .card__icon,
.card:active .card__icon {
  animation: bounce $duration $easing 2;
}
@keyframes bounce {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(-8px); }
}

07Gradient border reveal

How it works

A plain 2px gray border sits on the card at rest. A second, absolutely positioned layer carrying a two-color linear-gradient fades in over it on hover, through two parallel mask declarations that punch out everything except a thin ring.

Where it fits

Nothing about the card's box size or padding shifts, since that gradient layer only ever toggles opacity.

Gradient border reveal
Open in a new tab

07 · Gradient border reveal — Measured area 2.281% · moving frames 20/23Original by GODRICH · SCSS and React ship in the zip · See the password

mask-compositelinear-gradientopacity
.card__border {
  background: linear-gradient(135deg, $color, $subject-cream);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask-composite: exclude;
  opacity: 0;
  transition: opacity $duration $easing;
}
.card:hover .card__border,
.card:focus-visible .card__border {
  opacity: 1;
}

08Cursor spotlight

How it works

Ten lines of JavaScript write the pointer's position into two CSS custom properties on every pointermove, and a radial-gradient circle reads those variables live to trail the cursor across the card's dark surface. Because --mx, --my, and --sp are registered with @property, the browser interpolates the move itself instead of the light jumping between spots.

Where it fits

Keyboard focus centers that same spotlight in the card's middle rather than chasing a pointer that was never there.

Cursor spotlight
Open in a new tab

08 · Cursor spotlight — Measured area 9.937% · moving frames 17/23Original by GODRICH · SCSS and React ship in the zip · See the password

pointermoveCSS 변수radial-gradient
card.addEventListener('pointermove', function (e) {
  var r = card.getBoundingClientRect();
  card.style.setProperty('--mx', ((e.clientX - r.left) / r.width * 100) + '%');
  card.style.setProperty('--my', ((e.clientY - r.top) / r.height * 100) + '%');
  card.style.setProperty('--sp', 1);
});

09Press feedback

How it works

The card only reacts to :active, never :hover, shrinking to scale(.97) and flattening its shadow back to the pressed variant for as long as a finger or mouse button stays down.

Where it fits

Releasing reverses both properties on the same short transition, so the card visibly springs back the instant contact ends.

Press feedback
Open in a new tab

09 · Press feedback — Measured area 2.287% · moving frames 17/23Original by GODRICH · SCSS and React ship in the zip · See the password

:activetransform: scalebox-shadow 축소
.card {
  transform: scale(1);
  transition: transform $duration $easing, box-shadow $duration $easing;
}
.card:active {
  transform: scale(.97);
  box-shadow: $shadow-press;
}

Where it breaks — the trap

Item 07 is the one demo here built from two parallel declarations doing the same job for two different rendering engines. Keeping only one of them breaks the reveal without any warning in the console.

Two properties, two vocabularies

The masked ring depends on -webkit-mask-composite: xor for WebKit's older compositing keywords and mask-composite: exclude for the standard CSS Masking property that replaced it. Those two properties don't share a vocabulary at all. MDN's mask-composite reference lists only add, subtract, intersect, and exclude as valid values for the unprefixed property. xor is not one of them, even though it reads like the natural modern spelling of the WebKit keyword.

Why the ring disappears

Write mask-composite: xor out of habit, and a browser reading the unprefixed property treats the whole declaration as invalid, drops it outright, and never rounds it to the nearest match. So the ring stays invisible in any engine that only understands the standard property. The unprefixed property itself is younger than the prefixed one it replaces — Chrome and Edge 120+, Safari 15.4+, Firefox 53+ per caniuse. So a project still supporting older Chrome builds needs the -webkit- line kept in place beside it, not swapped out for it. A border that never has to be masked at all sidesteps the whole pairing: the neobrutalism UI kit sections draw their frames as plain opaque strokes, which is why nothing in that set depends on two vocabularies agreeing.

What's in the archive

Item 07's own folder in the archive already carries both lines correctly paired, opened with 45c9tna8 keyed in exactly as it's spelled on this line, no spaces added, no letters capitalized.

Accessibility

Two shapes under reduced motion

Accessibility here mostly means the same finished look staying reachable without a pointer, but the nine split into two shapes under reduced motion. 01, 05, 07, and 09 simply drop their transition, so hover, focus, or a press still lands on the exact same lifted, zoomed, bordered, or pressed state — just without the animated trip getting there. 04 goes one step further and pins its caption panel open at all times, so the extra description never depends on hover firing at all. 02's spinning ring and 06's bounce disappear differently. 02 freezes at a fixed, dim ring rather than either hiding completely or keeping its full spin. 06's two-cycle bounce is removed outright, since its own keyframes already return the icon to its starting position by the end.

The two the query never reaches

03's tilt and 08's spotlight are the two reading a live pointer position through JavaScript rather than a CSS transition. Reduced motion still strips their eased approach, but the values a mouse keeps writing into --rx, --ry, --mx, and --my keep updating in real time regardless. So a keyboard user gets one fixed fallback angle, while a mouse user's own movement is the one thing the media query never reaches. The setting itself is explained on MDN's prefers-reduced-motion page.

The rest of this card-hover set lives on the css hub, and what actually ships inside the download versus what only gets described here is covered on the about page.

FAQ

Can two of these css card hover effects run on the same card at once?

Yes — each effect only touches its own property or its own layer. 01 changes transform and box-shadow on the whole card, and 07 fades in a separately masked border layer, so neither competes with the other. The one pairing worth avoiding is two effects writing to the same custom property or the same transform on the same element, such as 03's tilt and 09's press. Whichever declaration comes second in the stylesheet simply overrides the first.

Do all nine of these css card hover effects need JavaScript to work?

No — only 03's tilt and 08's spotlight read a live pointer position, and both do it in eight to ten lines that write straight into CSS custom properties rather than touching layout. The other seven run entirely on :hover, :focus-visible, and :active, with no script involved at any point. Even 03 and 08 ship a CSS-only fallback: tabbing to the card without a mouse still lands on one fixed tilt angle or a centered spotlight. That fallback is written directly into the stylesheet rather than into the pointer listener. Controls placed inside a card follow the same no-script rule — the CSS toggle switches in that set flip on a checkbox state alone, so they sit inside 09's press feedback without a second listener.

Does a css product card hover effect pay more for box-shadow than for transform?

A little, yes — only 01 and 09 in this set touch box-shadow on hover or press. Unlike transform, changing a shadow forces the browser to repaint the area the shadow covers instead of just recompositing a layer. Scoped to one hovered card at a time, that repaint stays small enough not to show up as dropped frames. That's part of why the other seven effects lean on transform and opacity instead, once the motion needs to loop continuously rather than fire once per interaction.

Enter the archive password

The password is inside this article. You will find it as you read.