Spotlight Card
A soft glow follows the pointer inside the card and lights up the border right where the pointer is.
Make a React card with a pointer-following spotlight. On pointermove, write the pointer position relative to the card into --x and --y (in px). Add a ::before layer with a 320px radial-gradient circle at var(--x) var(--y), from the accent at 18% opacity to transparent, and fade it from opacity 0 to 1 on hover over 200ms. Light up the border at the same spot: use a 1px padding-box/border-box background trick (background: linear-gradient(surface, surface) padding-box, radial-gradient(160px circle at var(--x) var(--y), accent, border colour) border-box with a transparent border). The card shows the label "Plan", the title "Studio", the price "RM 49 / month" and the note "Unlimited projects and exports". Under prefers-reduced-motion, keep the glow centred and static.
Source
'use client'
import s from './demo.module.css'
export default function SpotlightCard({ labels }: { labels: Record<string, string> }) {
const onMove = (e: React.PointerEvent<HTMLDivElement>) => {
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return
const r = e.currentTarget.getBoundingClientRect()
e.currentTarget.style.setProperty('--x', `${e.clientX - r.left}px`)
e.currentTarget.style.setProperty('--y', `${e.clientY - r.top}px`)
}
return (
<div className={s.card} onPointerMove={onMove}>
<span className={s.kicker}>{labels.kicker}</span>
<strong className={s.name}>{labels.name}</strong>
<span className={s.price}>{labels.price}</span>
<span className={s.note}>{labels.note}</span>
</div>
)
}
.card {
--x: 50%;
--y: 50%;
position: relative;
display: grid;
gap: 6px;
width: min(260px, 100%);
padding: 22px;
overflow: hidden;
border: 1px solid transparent;
border-radius: 16px;
background:
linear-gradient(var(--stage-surface), var(--stage-surface)) padding-box,
radial-gradient(160px circle at var(--x) var(--y), var(--stage-accent), var(--stage-rule)) border-box;
}
.card::before {
content: '';
position: absolute;
inset: 0;
background: radial-gradient(320px circle at var(--x) var(--y), color-mix(in oklch, var(--stage-accent) 18%, transparent), transparent 70%);
opacity: 0;
pointer-events: none;
transition: opacity 200ms var(--ease-out);
}
.card:hover::before { opacity: 1; }
.card > * { position: relative; }
.kicker { font: 500 var(--text-xs) / 1 var(--font-mono); letter-spacing: 0.06em; text-transform: uppercase; color: var(--stage-muted); }
.name { font: 600 var(--text-xl) / 1.2 var(--font-display); color: var(--stage-ink); }
.price { font: 600 var(--text-base) / 1.2 var(--font-body); color: var(--stage-accent); }
.note { font-size: var(--text-sm); color: var(--stage-muted); }