A card whose surface and border pick up a soft glow that follows the pointer, driven by CSS variables with no re-render per move. Keyboard focus shows a steady glow.
Latency budgets
Set a p95 target per route and get a warning in review when a change would push it over.
p95 · 180 ms target
import { Gauge } from "lucide-react"
import { SpotlightCard } from "@/components/ballmac/spotlight-card"
export default function SpotlightCardDemo() {
return (
<SpotlightCard className="w-full max-w-sm">
<div className="flex size-9 items-center justify-center rounded-md border bg-background">
<Gauge className="size-4" aria-hidden="true" />
</div>
<h3 className="mt-4 text-base font-semibold tracking-tight">Latency budgets</h3>
<p className="mt-1.5 text-sm text-muted-foreground">
Set a p95 target per route and get a warning in review when a change would push it over.
</p>
<p className="mt-4 font-mono text-xs text-muted-foreground">p95 · 180 ms target</p>
</SpotlightCard>
)
}Installation
$ pnpm dlx shadcn@latest add @ballmac/spotlight-cardInstall the dependencies.
$ pnpm add motion@^12Copy the source into your project.
components/ballmac/spotlight-card.tsx// Ballmac UI: Spotlight Card. https://ui.ballmac.com/components/spotlight-card "use client" import * as React from "react" import { useReducedMotion } from "motion/react" import { cn } from "@/lib/utils" type SpotlightCardProps = React.ComponentProps<"div"> & { /** Radius of the glow in pixels. */ size?: number } // Shows only the element's border box ring: the padding box is cut out of the full box. const borderOnlyMask: React.CSSProperties = { WebkitMask: "linear-gradient(black, black) padding-box, linear-gradient(black, black)", WebkitMaskComposite: "xor", mask: "linear-gradient(black, black) padding-box exclude, linear-gradient(black, black)", } function isFocusVisible(element: Element) { try { return element.matches(":focus-visible") } catch { return false } } function SpotlightCard({ size = 320, className, style, children, onPointerMove, onFocus, ...props }: SpotlightCardProps) { const ref = React.useRef<HTMLDivElement>(null) const reduceMotion = useReducedMotion() // Written straight to CSS variables: no React render per pointer move. const moveTo = (x: string, y: string) => { ref.current?.style.setProperty("--spotlight-x", x) ref.current?.style.setProperty("--spotlight-y", y) } return ( <div ref={ref} data-slot="spotlight-card" className={cn( "group/spotlight relative isolate rounded-xl border bg-card p-6 text-card-foreground shadow-xs outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50", className )} style={ { "--spotlight-size": `${size}px`, "--spotlight-x": "50%", "--spotlight-y": "0%", ...style, } as React.CSSProperties } onPointerMove={(event) => { if (!reduceMotion && event.pointerType !== "touch") { const rect = event.currentTarget.getBoundingClientRect() moveTo(`${event.clientX - rect.left}px`, `${event.clientY - rect.top}px`) } onPointerMove?.(event) }} onFocus={(event) => { // Keyboard focus gets a steady glow from the top edge. if (isFocusVisible(event.target)) moveTo("50%", "0%") onFocus?.(event) }} {...props} > <span aria-hidden="true" data-slot="spotlight-card-surface" className="pointer-events-none absolute inset-0 -z-10 rounded-[inherit] opacity-0 transition-opacity duration-200 group-hover/spotlight:opacity-100 group-focus-visible/spotlight:opacity-100 group-has-[:focus-visible]/spotlight:opacity-100" style={{ background: "radial-gradient(var(--spotlight-size) circle at var(--spotlight-x) var(--spotlight-y), color-mix(in oklch, var(--ring) 11%, transparent), transparent 70%)", }} /> <span aria-hidden="true" data-slot="spotlight-card-border" className="pointer-events-none absolute -inset-px rounded-[inherit] border border-transparent opacity-0 transition-opacity duration-200 group-hover/spotlight:opacity-100 group-focus-visible/spotlight:opacity-100 group-has-[:focus-visible]/spotlight:opacity-100" style={{ ...borderOnlyMask, background: "radial-gradient(calc(var(--spotlight-size) * 0.75) circle at var(--spotlight-x) var(--spotlight-y), color-mix(in oklch, var(--ring) 75%, transparent), transparent 70%) border-box", }} /> {children} </div> ) } export { SpotlightCard, type SpotlightCardProps }Update the import paths to match your project setup.
Usage
import { SpotlightCard } from "@/components/ballmac/spotlight-card"The full example is in the Code tab above.
Examples
Feature grid
import { ArrowUpRight, KeyRound, Layers, ScrollText } from "lucide-react"
import { SpotlightCard } from "@/components/ballmac/spotlight-card"
const features = [
{ icon: KeyRound, title: "Scoped API keys", body: "Grant read or write per project, rotate without downtime.", href: "#keys" },
{ icon: Layers, title: "Preview environments", body: "Every pull request gets its own URL and database branch.", href: "#previews" },
{ icon: ScrollText, title: "Audit log", body: "Who changed what and when, exportable as CSV.", href: "#audit" },
]
export default function SpotlightCardGrid() {
return (
<div className="grid w-full max-w-3xl gap-4 sm:grid-cols-3">
{features.map(({ icon: Icon, title, body, href }) => (
<SpotlightCard key={title} className="flex flex-col p-5">
<Icon className="size-4 text-muted-foreground" aria-hidden="true" />
<h3 className="mt-3 text-sm font-semibold">{title}</h3>
<p className="mt-1 flex-1 text-sm text-muted-foreground">{body}</p>
<a
href={href}
className="mt-4 inline-flex items-center gap-1 self-start rounded-sm text-sm font-medium outline-none after:absolute after:inset-0 after:rounded-xl focus-visible:ring-[3px] focus-visible:ring-ring/50"
>
Learn more <ArrowUpRight className="size-3.5 rtl:-scale-x-100" aria-hidden="true" />
</a>
</SpotlightCard>
))}
</div>
)
}API reference
| Prop | Type | Default |
|---|---|---|
sizeRadius of the glow in pixels. | number | 320 |
Also accepts the standard attributes of its root element.
Use with AI
A drop-in card surface (rounded-xl, border, bg-card, p-6) with a pointer-following glow in the --ring color. Put any content inside; if the card contains a link or button, focusing it lights the card. With the shadcn MCP server set up (guide), ask your agent:
Add the Ballmac UI Spotlight Card (@ballmac/spotlight-card) to this project with the shadcn MCP, then use it where it fits.
Use it for
- Feature grids on landing pages
- Pricing or plan cards that should respond to hover
- Clickable tiles in a dashboard or settings index
Not for
- Dense data tables or long lists of cards (the effect loses meaning when everything glows)
- Plain content containers with no interaction (use card)
Registry JSON: https://ui.ballmac.com/r/spotlight-card.json
Credits
Free to use in personal and commercial projects.
- npm
- motion@^12
- Registry
- shadcn/utils
Pairs well with
Badge
A small pill label in four variants with an optional status dot and success, warning and error tones. Style links with badgeVariants().
Button
A button with six variants, three sizes, a pill shape and a built-in loading state. buttonVariants() styles links the same way.
Border Beam
A short beam of light that travels around the inside edge of its parent's border at a constant speed. Decorative, token-colored, hidden under reduced motion.
Animated Beam
An SVG beam that connects two elements with a curved path and sends a glowing gradient pulse along it. Follows layout changes, pauses off-screen, static under reduced motion.