Analytics
Move the pointer across this card. The light follows on both the surface and the edge.
A highlight that tracks the pointer across a card surface.
Card Spotlight
Two soft lights follow the cursor: a wash on the card surface and a glow along the border. The surface one is easy. The border one is the detail that separates a polished component from a first-attempt grid — and it needs a mask, not an overflow: hidden.
Move the pointer across this card. The light follows on both the surface and the edge.
Each card keeps its own position state — nothing is shared between siblings.
The effect costs two style writes per frame and no layout reads.
Add a pointer-tracked spotlight to a grid of cards — a soft wash across the surface plus a glow along the border. Surface highlight: - On pointermove, compute the pointer position relative to the card's bounding box and write it into two CSS custom properties on that card: --mx and --my, in pixels. - Render the wash with a ::before pseudo-element covering the card: radial-gradient(200px circle at var(--mx) var(--my), rgba(120,160,255,.16), transparent 65%). - Fade it in with opacity on :hover, not by toggling display — display changes cause layout work. Border glow: - Use a ::after pseudo-element with inset: 0, border-radius: inherit and padding: 1px. - Give it the same radial gradient but brighter, then punch out the middle with a mask: -webkit-mask: linear-gradient(#000,#000) content-box, linear-gradient(#000,#000); -webkit-mask-composite: xor; mask-composite: exclude; - Do not try to do this with overflow: hidden — that clips the glow to the inside edge and it disappears. Constraints: - Cache the bounding rect on pointerenter; never call getBoundingClientRect() inside pointermove. - Write only custom properties. Never set left/top on the highlight element. - Skip the whole effect under @media (hover: none) so touch devices are unaffected. - Respect prefers-reduced-motion: keep the static hover state, remove the transition timing. - Keep a visible :focus-visible outline — the spotlight is decorative and must never be the only indication of focus.
::after fills the whole box. padding: 1px plus mask-composite: exclude keeps only that 1px ring, which is why the glow appears to travel along the edge. overflow: hidden would cut it off at the inner edge and hide the effect entirely.getBoundingClientRect() forces layout. Calling it on every pointermove (which can fire at 120Hz+) makes the card feel heavy. Compute it once on pointerenter.--mx and --my on the card invalidates only paint for that card's pseudo-elements. No layout, no sibling recalculation.| Aspect | Detail |
|---|---|
| CSS custom properties | Universal. |
| mask-composite | Chrome 120+, Safari 15.4+, Firefox 53+ via the standard property. In older Safari only the -webkit- form applies — ship both. |
| Degradation | If masking is unsupported the border glow is simply absent; the surface wash still works and the card is unchanged. |
The one thing to watch is the number of cards. Twenty spotlit cards on one page means twenty pseudo-element pairs repainting as the pointer moves. That is still cheap, but on a low-end device it is the first thing to drop — in which case keep the effect on the hovered card only and remove it from the rest.
Don't use it to imply interactivity a card doesn't have. A light that follows the cursor reads as "this is clickable". If the card is not a link, add no cursor change and no hover lift — or better, remove the effect.
Don't stack it with a tilt and a glow and a scale. Three simultaneous pointer reactions on one card is the most common way this effect goes from premium to gimmicky.
Don't ship it without a keyboard story. Pointer-only feedback means keyboard users see nothing. Pair it with a clear :focus-visible outline of equal prominence.