Spotlight Card Grid
A card grid with a soft red spotlight that follows the cursor across it.
- Category
- Patterns
- Added
- 2026-08-21
- Deps
- none
- Updated
- 2026-09-01
Fast
Ships in minutes, not sprints.
Sharp
Every pixel earns its place.
Alive
Motion that means something.
Yours
Copy it, bend it, ship it.
Props
"use client";
import { useRef, useState } from "react";
const ITEMS = [
{ title: "Fast", body: "Ships in minutes, not sprints." },
{ title: "Sharp", body: "Every pixel earns its place." },
{ title: "Alive", body: "Motion that means something." },
{ title: "Yours", body: "Copy it, bend it, ship it." },
];
export function SpotlightGrid({
radius = 220,
columns = 2,
intensity = 0.16,
}: {
radius?: number;
columns?: number;
/** Peak alpha of the spotlight wash, 0-1. */
intensity?: number;
}) {
const containerRef = useRef<HTMLDivElement>(null);
const [pos, setPos] = useState({ x: -9999, y: -9999 });
const [active, setActive] = useState(false);
function handleMove(e: React.MouseEvent<HTMLDivElement>) {
const el = containerRef.current;
if (!el) return;
const rect = el.getBoundingClientRect();
setPos({ x: e.clientX - rect.left, y: e.clientY - rect.top });
}
return (
<div
ref={containerRef}
onMouseMove={handleMove}
onMouseEnter={() => setActive(true)}
onMouseLeave={() => setActive(false)}
className="relative grid gap-3 rounded-2xl border border-black/10 bg-neutral-100 p-3"
style={{ gridTemplateColumns: "repeat(" + columns + ", minmax(0, 1fr))" }}
>
<div
className="pointer-events-none absolute inset-0 rounded-2xl transition-opacity duration-300"
style={{
opacity: active ? 1 : 0,
background: `radial-gradient(${radius}px circle at ${pos.x}px ${pos.y}px, rgba(255,45,45,${intensity}), transparent 70%)`,
}}
/>
{ITEMS.map((item) => (
<div key={item.title} className="relative z-10 rounded-xl border border-black/10 bg-white p-4">
<h4 className="mb-1 text-base font-bold">{item.title}</h4>
<p className="text-sm text-black/60">{item.body}</p>
</div>
))}
</div>
);
}
About this pattern
Static card grids read as inert until something moves under the cursor. This pattern tracks mouse position inside the grid container and paints a single radial-gradient overlay that follows it, so the whole grid appears lit by one warm light passing across a cool surface rather than each card getting its own hover state. Reach for this instead of a per-card hover background when the light needs to feel continuous as the cursor crosses card boundaries — a CSS-only approach re-triggers per element and the seam between cards shows. The detail worth knowing: there is exactly one overlay div for the entire grid, not one per card, sitting above the cards' background but below their content, which is what makes it read as a single light source instead of four independent glows. The catch is it's mouse-only — the position handlers never fire on touch, so on mobile the grid just sits flat with no spotlight and no fallback state.
Curator’s note
One overlay div for the whole grid, not one spotlight per card — cheaper to paint and reads as a single continuous light source.
Pairs well with
Matched on shared tags and category — the entries most likely to be used alongside this one.
Spotlight Follow Background
backgroundsA full-bleed radial glow that tracks the cursor with zero re-renders.
Hover Reveal Text Gallery
patternsA list of text rows, each hiding a photograph that wipes open under the cursor and follows it.
Masonry Image Hover Grid
assetsA CSS-columns masonry gallery with a scale + caption reveal on hover.