# Spotlight Card

> Card whose border and surface light up under the cursor.

Category: Effects. Docs: https://neonkit.heyaadi.com/components/spotlight-card

## Installation

```bash
npx shadcn@latest add https://neonkit.heyaadi.com/r/spotlight-card.json
```

With the `@neonkit` namespace configured: `npx shadcn@latest add @neonkit/spotlight-card`.

npm dependencies: none. Also needs `lib/utils.ts` (`cn`) and `lib/neon.ts` (`neonVars`), which the CLI adds.

## Usage

```tsx
import { SpotlightCard } from "@/components/ui/spotlight-card"

<SpotlightCard className="p-6">Hover me</SpotlightCard>
```

## Examples

### Stats

Each card keeps its own light; the border catches it nearest the pointer.

```tsx
import { SpotlightCard } from "@/components/ui/spotlight-card";

const regions = [
  { code: "fra1", ms: 38 },
  { code: "iad1", ms: 44 },
  { code: "hnd1", ms: 51 },
  { code: "gru1", ms: 63 },
];

export default function SpotlightCardDemo() {
  return (
    <div className="grid w-full max-w-3xl gap-3 sm:grid-cols-[1.35fr_1fr]">
      <SpotlightCard className="flex flex-col justify-between gap-8 p-6 sm:row-span-2">
        <div>
          <p className="text-sm text-white/55">Median time to first byte</p>
          <p className="mt-2 text-5xl font-semibold tabular-nums tracking-tight text-white/90">
            41<span className="ml-1 text-xl font-medium text-white/55">ms</span>
          </p>
        </div>
        <ul className="grid gap-2.5">
          {regions.map((region) => (
            <li key={region.code} className="grid grid-cols-[3rem_1fr_3rem] items-center gap-3 text-xs">
              <span className="font-mono text-white/60">{region.code}</span>
              <span className="h-1 rounded-full bg-white/[0.06]">
                <span
                  className="block h-full rounded-full bg-[rgb(var(--neon-accent,34_211_238)/0.7)]"
                  style={{ width: `${(region.ms / 70) * 100}%` }}
                />
              </span>
              <span className="text-right font-mono tabular-nums text-white/70">{region.ms} ms</span>
            </li>
          ))}
        </ul>
      </SpotlightCard>
      <SpotlightCard className="p-5">
        <p className="text-sm font-medium text-white/90">Signed builds</p>
        <p className="mt-1 text-sm leading-relaxed text-white/55">1,284 artifacts this week, each traceable to its commit.</p>
      </SpotlightCard>
      <SpotlightCard className="p-5">
        <p className="text-sm font-medium text-white/90">A preview per branch</p>
        <p className="mt-1 text-sm leading-relaxed text-white/55">37 live right now. Delete the branch and its URL goes with it.</p>
      </SpotlightCard>
    </div>
  );
}
```

### Wider light

radius sets the size of the pool of light.

```tsx
import { SpotlightCard } from "@/components/ui/spotlight-card";

export default function SpotlightCardProfile() {
  return (
    <SpotlightCard radius={480} className="w-full max-w-sm p-6">
      <p className="text-sm text-white/55">Now playing</p>
      <p className="mt-3 text-2xl font-medium tracking-tight text-white/90">Midnight City</p>
      <p className="text-sm text-white/55">M83 · Hurry Up, We&apos;re Dreaming</p>
      <div className="mt-6 flex items-center gap-3 text-xs tabular-nums text-white/55">
        <span>1:42</span>
        <div className="h-1 flex-1 rounded-full bg-white/10">
          <div className="h-full w-[42%] rounded-full bg-[rgb(var(--neon-accent,34_211_238))] shadow-[0_0_10px_rgb(var(--neon-accent,34_211_238)/0.7)]" />
        </div>
        <span>4:03</span>
      </div>
    </SpotlightCard>
  );
}
```

## API reference

### SpotlightCard

Also accepts every div attribute.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `color` | `NeonColor` | `inherited` | Light color. |
| `radius` | `number` | `360` | Diameter of the light in pixels. |

## Accessibility

- The pointer position is written to CSS variables directly, so moving the cursor never re-renders React.
- The light also appears when something inside the card has keyboard focus.

## Source

`components/ui/spotlight-card.tsx`

```tsx
"use client";

import * as React from "react";

import { cn } from "@/lib/utils";
import { neonVars, type NeonColor } from "@/lib/neon";

export interface SpotlightCardProps
  extends Omit<React.HTMLAttributes<HTMLDivElement>, "color"> {
  /** Palette name or any hex color. Inherits `--neon-accent` when omitted. */
  color?: NeonColor;
  /** Diameter of the light in pixels. */
  radius?: number;
}

const SpotlightCard = React.forwardRef<HTMLDivElement, SpotlightCardProps>(
  (
    { className, style, color, radius = 360, children, onPointerMove, ...props },
    forwardedRef,
  ) => {
    const ref = React.useRef<HTMLDivElement>(null);
    React.useImperativeHandle(forwardedRef, () => ref.current as HTMLDivElement);

    // Write the pointer position straight to CSS variables: no re-renders.
    const handlePointerMove = (event: React.PointerEvent<HTMLDivElement>) => {
      onPointerMove?.(event);
      const node = ref.current;
      if (!node) return;
      const rect = node.getBoundingClientRect();
      node.style.setProperty("--x", `${event.clientX - rect.left}px`);
      node.style.setProperty("--y", `${event.clientY - rect.top}px`);
    };

    return (
      <div
        ref={ref}
        onPointerMove={handlePointerMove}
        style={neonVars(color, { "--r": `${radius / 2}px`, ...style } as React.CSSProperties)}
        className={cn(
          "[--neon:var(--neon-accent,34_211_238)] [--x:50%] [--y:50%]",
          "group/spotlight relative isolate overflow-hidden rounded-2xl border border-white/10 bg-white/[0.03]",
          className,
        )}
        {...props}
      >
        {/* Light on the surface */}
        <div
          aria-hidden
          className="pointer-events-none absolute inset-0 -z-10 opacity-0 transition-opacity duration-300 group-hover/spotlight:opacity-100 group-focus-within/spotlight:opacity-100 [background:radial-gradient(var(--r)_circle_at_var(--x)_var(--y),rgb(var(--neon)/CALC(0.12*var(--neon-glow,1))),transparent_70%)]"
        />
        {/* Light caught by the border */}
        <div
          aria-hidden
          className="pointer-events-none absolute inset-0 rounded-[inherit] p-px opacity-0 transition-opacity duration-300 group-hover/spotlight:opacity-100 group-focus-within/spotlight:opacity-100 [background:radial-gradient(calc(var(--r)*0.75)_circle_at_var(--x)_var(--y),rgb(var(--neon)/var(--neon-glow,1)),transparent_70%)] [mask:linear-gradient(#000_0_0)_content-box_exclude,linear-gradient(#000_0_0)]"
        />
        {children}
      </div>
    );
  },
);
SpotlightCard.displayName = "SpotlightCard";

export { SpotlightCard };
```
