Spotlight Card
A cursor spotlight card — a border glow and a fainter background bloom track the cursor together and fade out on leave, with each card in a row staying independent.
Bring this into your project
Run this command in your project folder. The CLI adds the source and dependencies; then follow the usage example below.
npx webberui@latest add spotlight-card.enSpotlight Card is a React spotlight card (cursor glow card) built with Tailwind CSS. The glow is a CSS radial-gradient driven by CSS variables, so no animation library is needed.
Playground
Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.
<SpotlightCard/>
The complete example includes the preview layout and data. Install the component first, then paste this file into your project.
When to use
- A feature grid on a SaaS homepage, where each card lights up under the cursor on its own.
- Clickable cards in a blog, portfolio, or template gallery, using the glow to show which one is pointed at.
- Stat cards or integration lists on a dark-themed dashboard.
- Not a good fit: if you want the card to tilt in 3D toward the cursor, use Tilt Card instead.
Installation
npx webberui@latest add https://webberui.com/r/spotlight-card.en.jsonOr, once registries are configured in components.json, install it as @webberui/spotlight-card.
Usage
Put the content straight into children and write the size and layout on className:
import { SpotlightCard } from "@/components/ui/spotlight-card";
<SpotlightCard className="max-w-sm">
<h3 className="text-sm font-semibold">Title</h3>
<p className="text-sm text-neutral-500">Move the cursor into the card and the glow follows along.</p>
</SpotlightCard>Customize the glow color and radius:
<SpotlightCard spotlightColor="rgba(59,130,246,0.6)" radius={240}>
<h3 className="text-sm font-semibold">Brand-colored glow</h3>
<p className="text-sm text-neutral-500">Any valid CSS color value works.</p>
</SpotlightCard>Examples
Feature grid
Cards placed side by side work independently, with no shared state to set up.
import { SpotlightCard } from "@/components/ui/spotlight-card";
const features = [
{ title: "Real-time sync", body: "Edit together; changes reach every device within seconds." },
{ title: "Permissions", body: "Set viewer, editor, and admin roles per project." },
{ title: "Audit log", body: "Every change records a timestamp and who made it." },
];
export function FeatureGrid() {
return (
<div className="grid gap-4 sm:grid-cols-3">
{features.map((f) => (
<SpotlightCard key={f.title}>
<h3 className="text-sm font-semibold">{f.title}</h3>
<p className="mt-2 text-sm text-neutral-500">{f.body}</p>
</SpotlightCard>
))}
</div>
);
}Make the whole card clickable
SpotlightCard renders a div, so wrapping it in a Next.js Link turns the whole card into a link that can also take keyboard focus.
import Link from "next/link";
import { SpotlightCard } from "@/components/ui/spotlight-card";
<Link href="/templates/dashboard" className="block rounded-xl focus-visible:outline-2 focus-visible:outline-offset-2">
<SpotlightCard className="h-full">
<h3 className="text-sm font-semibold">Dashboard template</h3>
<p className="mt-2 text-sm text-neutral-500">Sidebar navigation, data tables, and chart pages.</p>
</SpotlightCard>
</Link>Tie the glow color to your theme
spotlightColor takes any CSS color value, including a CSS variable, so the glow can follow your design tokens.
<SpotlightCard spotlightColor="var(--primary)" radius={200} className="max-w-sm">
<h3 className="text-sm font-semibold">Brand-colored glow</h3>
<p className="mt-2 text-sm text-neutral-500">The glow color comes from a theme variable.</p>
</SpotlightCard>Props
| Prop | Type | Default | Description |
|---|---|---|---|
spotlightColor | string | dark gray in light mode / white in dark mode | Glow color; accepts any valid CSS color value |
radius | number | 320 | Glow radius (px) |
children | React.ReactNode | — | Card content |
className | string | — | Appended to the outermost container's className |
Remaining props (such as onClick and id) are all forwarded to the outermost div.
How it works
- Performance: mousemove never goes through React state. Coordinates are written straight into the CSS variables
--wb-spot-x/--wb-spot-ywithref.style.setPropertyand read back by aradial-gradient, so no matter how fast the cursor moves, no re-render is triggered - The border glow is implemented as a "1px padding container": the outer layer lays down a neutral base color as a static border, a cursor-following radial-gradient is stacked on top of it, and the inner card covers the middle so the gradient only shows through the 1px ring
- The background bloom shares the same coordinate variables as the border glow, with the radius scaled up 1.75× and the opacity halved, producing a fainter, broader glow
- Fading in and out runs on an opacity transition (the
--wb-duration-fasttoken, 200ms by default):mouseentersets--wb-spot-opacityto 1 andmouseleavesets it back to 0, so both glow layers fade together - Cards placed side by side stay independent: every variable is written on each card's own container, and no global state is shared
- The default glow color is defined by class (dark gray in light mode, white in dark mode); passing
spotlightColoroverrides both through an inline style - The glow is kept even when the user has "reduce motion" enabled at the system level — it is a hover position cue that follows the cursor, not an animation that plays on its own, and only an opacity transition is involved; the DOM structure is identical before and after, so there is no SSR hydration difference
- Touch devices have no hover, so the card presents its static border and its content and interactions are unaffected; every glow layer is
aria-hiddenandpointer-events-none, so it does not interfere with assistive technology or clicks
FAQ
Does it work with the Next.js App Router?
Yes. The source already has "use client", so import it from a Server Component page directly; the server and client render the same DOM, so there is no hydration mismatch.
Do I need to install Motion?
No. The component only depends on clsx and tailwind-merge (for cn); the glow and the fade are plain CSS.
Does fast cursor movement cause lots of re-renders?
No. mousemove writes the --wb-spot-x and --wb-spot-y CSS variables directly with style.setProperty, bypassing React state.
How do I change the card background, padding, or radius?
className goes on the outer container and controls size and layout. The inner card's bg-white p-6 dark:bg-neutral-900 and its radius live in the source, so edit them in your copied file.
What happens on phones?
Touch devices have no hover, so the card shows its static 1px border; content and taps work as usual.
Card Deck
A draggable stacked card deck — flick a card out in any of four directions, the next one fills in automatically, with infinite loop or draw-until-empty modes.
Infinite Drag Canvas
An infinitely pannable portfolio canvas that scatters cards across a boundless space, with drag (including momentum), keyboard panning, and one-click recentering.