Microinteractions · Starter
Spotlight Card
Pointer position is written to CSS custom properties; a radial gradient reads them. No React re-renders on move.
Live preview
Move a mouse across the cards, or Tab between them.
Copy-ready · type-checkedThe preview above renders this exact file. Reduced-motion fallback included.
/**
* Spotlight Card
* A soft theme-token spotlight follows attention across three cards.
* Requires: motion, Tailwind with shadcn semantic tokens.
*/
import { useReducedMotion } from "motion/react";
export default function SpotlightCard({
reduced: reducedProp,
intensity = 1,
}: {
reduced?: boolean;
intensity?: number;
}) {
const systemReduced = useReducedMotion();
const reduced = reducedProp ?? systemReduced ?? false;
const amount = Math.min(1.5, Math.max(0.5, intensity));
return (
<section className="flex min-h-[80vh] items-center justify-center bg-background px-6 py-20 text-foreground">
<style>{`
.ardent-spotlight {
background: radial-gradient(circle 260px at var(--spot-x, 50%) var(--spot-y, 50%), var(--foreground), transparent 75%);
}
`}</style>
<div className="w-full max-w-6xl">
<p className="mb-6 font-mono text-xs uppercase tracking-[0.2em] text-muted-foreground">
Where attention goes / 04
</p>
<h2 className="mb-10 font-serif text-5xl tracking-tight">Considered from every angle.</h2>
<div className="grid gap-4 md:grid-cols-3">
{[
["Strategy", "Find the idea worth building around."],
["Identity", "Make the essential unmistakable."],
["Motion", "Give every interaction intention."],
].map(([title, description], index) => (
<article
key={title}
tabIndex={0}
onFocus={(event) => {
event.currentTarget.style.setProperty("--spot-x", "50%");
event.currentTarget.style.setProperty("--spot-y", "50%");
}}
onPointerMove={(event) => {
if (
reduced ||
event.pointerType !== "mouse" ||
!window.matchMedia("(hover: hover) and (pointer: fine)").matches
)
return;
const rect = event.currentTarget.getBoundingClientRect();
event.currentTarget.style.setProperty("--spot-x", `${event.clientX - rect.left}px`);
event.currentTarget.style.setProperty("--spot-y", `${event.clientY - rect.top}px`);
}}
className="group relative isolate overflow-hidden rounded-2xl border border-border bg-card p-8 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-4"
>
{!reduced && (
<div className="pointer-events-none absolute inset-0 -z-10 opacity-0 transition-opacity duration-300 group-hover:opacity-100 group-focus-visible:opacity-100">
<div
className="ardent-spotlight absolute inset-0"
style={{ opacity: 0.09 * amount }}
/>
</div>
)}
<p className="font-mono text-xs uppercase tracking-[0.2em] text-muted-foreground">
0{index + 1} / Discipline
</p>
<h3 className="mt-24 font-serif text-4xl tracking-tight">{title}</h3>
<p className="mt-5 text-sm leading-relaxed text-muted-foreground">{description}</p>
</article>
))}
</div>
</div>
</section>
);
}
Next patternBlend-Mode Cursor→