Skip to content

Spotlight Card Grid

A card grid with a soft red spotlight that follows the cursor across it.

Freeglassminimal
Category
Patterns
Added
2026-08-21
Deps
none
Updated
2026-09-01
Preview

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

220
2
0.16
No runtime dependencies
"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>
  );
}
Notes

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.

Related

Pairs well with

Matched on shared tags and category — the entries most likely to be used alongside this one.

Pro

A full-bleed radial glow that tracks the cursor with zero re-renders.

spotlightcursorradial-gradient
Featured
NewPro

A list of text rows, each hiding a photograph that wipes open under the cursor and follows it.

galleryhoverlist

A CSS-columns masonry gallery with a scale + caption reveal on hover.

masonrygallerygrid