Light that follows the pointer

Move a radial highlight with CSS variables, not re-renders.

Sam Selikoff@samselikoff
Just posted a new recipe: Spotlight! A subtle hover treatment with a radial gradient that follows your mouse. Uses Framer Motion to update the gradient's position outside of React's render cycle.
Apr 26, 2023View on X ↗

1 · Flat or lit

Move over the cards, or tap one to place the light.

Flat hover fillBad
:hover { background }
SpotlightGood
radial-gradient at var(--x) var(--y)
card.addEventListener("pointermove", (e) => {
  const r = card.getBoundingClientRect();
  card.style.setProperty("--x", `${e.clientX - r.left}px`);
  card.style.setProperty("--y", `${e.clientY - r.top}px`);
});

.card::before {
  background: radial-gradient(240px circle at var(--x) var(--y),
    color-mix(in oklch, var(--fg) 10%, transparent), transparent 70%);
}

2 · Where the light moves

Sweep across each card and watch the counters.

Rebuild the cardBad
Nodes rebuilt0
Set a CSS variableGood
Nodes rebuilt0Variable writes0

Why it matters, pros and cons

Why it matters

A soft light under the pointer makes a card feel touchable, and a variable keeps it cheap.

Pros

  • Two property writes per move; the gradient reads them, so no DOM work.
  • Text, focus and selection inside the card are never rebuilt or lost.
  • One shared rule lights every card, with no per-card state.

Cons

  • Touch has no hover, so the light needs a tap or it never appears there.
  • A gradient repaints on every move, so keep cards small and few.
  • On a dense grid the effect turns to noise; use it on a handful of cards.