Home / Snippets / Animation /

Shadow transition

Hover-triggered box-shadow animations — lift, glow, colored shadow, and inset effects.

Lift
Glow
Color shift
Inset
Widely Supported
animationno-js

Quick implementation

/* Lift effect — shadow grows + element rises */
.shadow-lift {
  box-shadow: 0 2px 4px oklch(0 0 0 / 0.2);
  transition: box-shadow 0.3s ease, transform 0.3s ease;
}
.shadow-lift:hover,
.shadow-lift:focus-visible {
  box-shadow: 0 12px 24px oklch(0 0 0 / 0.35);
  transform: translateY(-4px);
}

/* Glow effect — colored shadow expands */
.shadow-glow {
  box-shadow: 0 0 0 oklch(0.72 0.19 265 / 0);
  transition: box-shadow 0.3s ease;
}
.shadow-glow:hover,
.shadow-glow:focus-visible {
  box-shadow: 0 0 20px oklch(0.72 0.19 265 / 0.5);
}

/* Inset shadow */
.shadow-inset {
  box-shadow: inset 0 0 0 oklch(0.72 0.19 265 / 0);
  transition: box-shadow 0.3s ease;
}
.shadow-inset:hover,
.shadow-inset:focus-visible {
  box-shadow: inset 0 0 16px oklch(0.72 0.19 265 / 0.3);
}

@media (prefers-reduced-motion: reduce) {
  .shadow-lift,
  .shadow-glow,
  .shadow-inset {
    transition-duration: 0.01s;
  }
}

Prompt this to your LLM

Includes role, constraints, two framework variants, and edge cases to handle.

You are a senior frontend engineer building interactive card components.

Goal: Hover-triggered box-shadow transitions that create lift, glow, and inset depth effects — CSS only, no JavaScript.

Technical constraints:
- Transition box-shadow with 0.25–0.4s ease timing.
- For lift effects, pair the shadow with transform: translateY(-4px) for a physical "raised" feel.
- Start the glow shadow at 0 spread and transparent color, transition to visible spread.
- Use oklch() for all shadow colors — no hex or rgba().
- Include :focus-visible so keyboard users see the effect.

Framework variant (pick one):
A) Vanilla HTML + CSS only.
B) React component — accept variant prop ("lift" | "glow" | "inset") and render a <div> with the appropriate class.

Edge cases to handle:
- Respect prefers-reduced-motion: collapse duration to near-instant and skip translateY.
- box-shadow triggers repaint each frame — for heavy use, consider the pseudo-element trick with opacity transition instead.
- Ensure the shadow doesn't clip on overflow: hidden parents.

Return CSS only.

Why this matters in 2026

Shadow transitions are the backbone of hover feedback on cards, buttons, and interactive tiles. A subtle lift-on-hover tells users an element is clickable without relying on color change alone. With oklch() shadow colors you get consistent darkness and saturation across hues, and the new color-mix() functions let you derive shadow colors from your design tokens automatically.

The logic

The browser interpolates each component of box-shadow independently — offset-x, offset-y, blur, spread, and color. For the lift effect, combine the shadow transition with transform: translateY(-4px) so the element physically moves upward while its shadow deepens. For glow, start with a zero-spread transparent shadow and transition to a visible one — this avoids the popping artefact of adding a shadow that wasn't there before. The inset variant works identically but paints the shadow inside the element.

Accessibility & performance

box-shadow transitions trigger repaint every frame, which is more expensive than compositor-only properties like transform and opacity. For a few cards on a page this is fine, but if you're animating dozens simultaneously, consider the pseudo-element opacity trick: render the final shadow on a ::after layer and transition its opacity from 0 to 1. Always include :focus-visible alongside :hover and gate the animation behind prefers-reduced-motion.