Entry 04

Shadow change that fades and does not repaint

Use it on cards that lift on hover, most of all in a grid of many cards or with a large blur.

Open
Hover a card. Its shadow deepens with a fade and comes to rest, and fades back when you leave. Nothing else about the card changes.
Code CSS · 37 lines
.card {
  position: relative;
  isolation: isolate;
}

.card::before,
.card::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: inherit;
  pointer-events: none;
}

.card::before {
  box-shadow:
    0 1px 4px oklch(0.3 0.05 250 / 0.1),
    0 2px 10px oklch(0.3 0.05 250 / 0.08);
}

.card::after {
  box-shadow:
    0 4px 8px oklch(0.3 0.05 250 / 0.06),
    0 12px 32px oklch(0.3 0.05 250 / 0.16);
  opacity: 0;
}

@media (hover: hover) and (pointer: fine) {
  .card:hover::before { opacity: 0; }
  .card:hover::after { opacity: 1; }
}

@media (prefers-reduced-motion: no-preference) {
  .card::before,
  .card::after { transition: opacity 0.3s var(--ease-out, ease-out); }
}
Why it works, the rules and the credits 3 reasons · 3 rules · support · credits

Why it works

  1. A box-shadow transition repaints the shadow on every frame. Each shadow here is painted once, and the transition changes only opacity, which the compositor handles. In a Chrome trace of one card it took 12 paint events, against about 78 for a plain box-shadow transition.
  2. The two shadows cross-fade, so the end state is exactly the raised shadow and not the sum of both.
  3. isolation: isolate gives the card its own stacking context. z-index: -1 then puts the pseudo-elements above the card's background and below its content.

Rules

  1. The card needs its own background. The pseudo-elements have none.
  2. It uses both pseudo-elements of the card. Hit area larger than the visual uses ::after for the hit area and Whole card clickable from one link uses it for the link, so do not combine them on one element.
  3. For a single small element, transition box-shadow directly. The saving is real but small per card.

Support

Every browser.

Credits

Borrowed from: Stripe, on its resource cards · Tobias Ahlin, How to animate box-shadow, for the older form of the idea