MynduiMyndui
0

Anatomy of Spotlight Card

How a radial glow tracks the pointer via CSS variables with no React state, and how the same gradient is masked down to a 1px border ring.

01/04Three layers, one surface
Glow
radial at var(--x)/var(--y), opacity 0→100 on group-hover
Border
same gradient, masked to 1px ring (mask-composite exclude)
Content
relative z-raised — sits above both glow layers
tsx
const GLOW_BASE ="… opacity-0 … group-hover:opacity-100 … [transition:opacity_400ms_ease] " +"[background:radial-gradient(var(--spotlight-radius)_circle_at_var(--x,50%)_var(--y,50%),…)]";<div aria-hidden className={GLOW_BASE} />{border ? <div aria-hidden className={BORDER_BASE} /> : null}<div className="relative z-raised">{children}</div>
01

Three layers, one surface

SpotlightCard never re-renders on pointer move. It writes --x / --y straight onto the root with style.setProperty, and two absolute layers — a fill glow and an optional border ring — read those vars from a shared radial gradient. Content sits above both at z-raised.

The root is a group relative overflow-hidden card. Inside: a pointer-events-none glow, an optional border twin, and a relative content wrapper. The glow defaults --x/--y to 50% so the first paint isn't a hard corner flash.

02/04Follow without React state

el.style.setProperty("--x", px + "px")

Glow layer
inset-0 radial at --x/--y · defaults 50%/50%
setProperty
direct DOM write on pointer move — zero re-renders
Hover fade
opacity 0→100, 400ms ease (group-hover)
tsx
const handlePointerMove = (e: React.PointerEvent<HTMLDivElement>) => {const el = ref.current;if (el) {  const rect = el.getBoundingClientRect();  el.style.setProperty("--x", `${e.clientX - rect.left}px`);  el.style.setProperty("--y", `${e.clientY - rect.top}px`);}onPointerMove?.(e);};
02

Follow without React state

On every pointermove, the handler measures the card rect and writes pixel offsets. No useState, no context — the compositor just repaints the gradient at the new center. Hover fades the glow in over 400ms ease; motion-reduce kills the transition.

glowColor and radius land as --spotlight-color / --spotlight-radius on the root style, so the same class string works for any tint or size.

03/04The border is the glow, masked to 1px

mask-composite: exclude · -webkit-mask-composite: xor

Fill glow
full-area radial at the same --x/--y
Ring mask
inset-0 · p-px · mask-composite: exclude
Moving center
only --x/--y change — layer stays put
tsx
const BORDER_BASE ="… [background:radial-gradient(…_at_var(--x,50%)_var(--y,50%),…)] " +"[-webkit-mask:linear-gradient(#fff_0_0)_content-box,linear-gradient(#fff_0_0)] " +"[-webkit-mask-composite:xor] " +"[mask:linear-gradient(#fff_0_0)_content-box,linear-gradient(#fff_0_0)] " +"[mask-composite:exclude] p-px";
03

The border is the glow, masked to 1px

When border is true (the default), a second layer reuses the identical radial gradient, then punches out the interior with a dual-layer mask and p-px. mask-composite: exclude (and -webkit-mask-composite: xor) leaves only the 1px ring lit — so the edge brightens where the pointer is closest without a separate stroke color.

Pass border={false} and the ring layer is omitted entirely — fill glow only.

Driving --x/--y through React state would re-render the whole card (and its children) on every pointer frame. Writing CSS variables on the DOM node keeps React out of the hot path: the glow and border are pure paint, content never reconciles, and the hover opacity transition stays a CSS animation.

04/04Result

Default glow

Radial follows the pointer; border lights with it.

Fill only

border=false drops the ring layer entirely.

04

The result

Two CSS variables, one radial, an optional masked ring, and content that never re-renders for the pointer. Move across either card.

Reduced motion

Both glow layers use motion-reduce:[transition:none]. The spotlight still tracks if the pointer moves — position isn't motion theater — but the 400ms fade snaps instead of easing.

Motion Score

Spotlight CardSSCompositor-only
SopacityFade / cross-fade
Each property is graded by how the browser runs it, from S (composited off the main thread) down to F (layout thrashing); the component takes the worst. MotionScore methodology →