Home Recipes Spotlight Card Microinteractions

Spotlight Card

A card with a radial glow that follows the cursor, built with CSS custom properties and pointer tracking.

Interactive demo

Pro plan

Reactive surface

Move your cursor over this card. The radial glow follows the pointer using CSS custom properties updated from a single pointermove listener.

What it does

A card whose border and surface are illuminated by a radial glow that follows the cursor, creating a reactive glass surface.

When to use it

  • Feature cards in landing pages.
  • Pricing or plan cards that need visual distinction.
  • Interactive tiles where hover feedback improves perceived quality.

When to avoid it

  • Cards in long lists where the effect could become distracting.
  • Touch-only interfaces where hover tracking is unavailable.
  • Low-contrast designs where the glow may hide text.

How it works

  1. Track pointer position inside the card on pointermove.
  2. Convert to percentages relative to the card width and height.
  3. Update CSS custom properties --spotlight-x and --spotlight-y .
  4. Render the glow with a radial-gradient background layered behind the border.
  5. Fade on hover โ€” only show the glow when the card is hovered or focused.
  6. Reduced motion โ€” keep a static subtle gradient instead of cursor tracking.

Source code

HTMLCSSTypeScript
<article class="spotlight-card" data-spotlight>
  <div class="spotlight-card__content">
    <span class="spotlight-card__tag">Feature</span>
    <h2 class="spotlight-card__title">Spotlight surface</h2>
    <p class="spotlight-card__text">The glow follows your cursor.</p>
  </div>
</article>
:root {
  color-scheme: dark;
  --bg: #0a0a0b;
  --surface: #16161a;
  --border: rgba(255, 255, 255, 0.1);
  --text: #f4f4f5;
  --muted: #a1a1aa;
  --accent: #8b5cf6;
}

body {
  margin: 0;
  min-height: 100dvh;
  display: grid;
  place-items: center;
  font-family: system-ui, sans-serif;
  background: var(--bg);
  color: var(--text);
}

.spotlight-card {
  --spotlight-x: 50%;
  --spotlight-y: 50%;

  position: relative;
  width: 320px;
  padding: 1px;
  border-radius: 1rem;
  background: radial-gradient(
    600px circle at var(--spotlight-x) var(--spotlight-y),
    rgba(139, 92, 246, 0.35),
    transparent 40%
  );
}

.spotlight-card::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  padding: 1px;
  background: radial-gradient(
    400px circle at var(--spotlight-x) var(--spotlight-y),
    rgba(255, 255, 255, 0.25),
    transparent 40%
  );
  -webkit-mask:
    linear-gradient(#fff 0 0) content-box,
    linear-gradient(#fff 0 0);
  -webkit-mask-composite: xor;
  mask-composite: exclude;
  pointer-events: none;
}

.spotlight-card__content {
  position: relative;
  padding: 2rem;
  background: var(--surface);
  border-radius: calc(1rem - 1px);
}

.spotlight-card__tag {
  font-size: 0.75rem;
  color: var(--accent);
}

.spotlight-card__title {
  margin: 0.5rem 0 0.25rem;
  font-size: 1.5rem;
}

.spotlight-card__text {
  margin: 0;
  color: var(--muted);
}

@media (prefers-reduced-motion: reduce) {
  .spotlight-card {
    background: radial-gradient(
      circle at 50% 0%,
      rgba(139, 92, 246, 0.18),
      transparent 50%
    );
  }
}
function prefersReducedMotion(): boolean {
  return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
}

const card = document.querySelector<HTMLElement>("[data-spotlight]");

if (card && !prefersReducedMotion()) {
  card.addEventListener("pointermove", (event) => {
    const rect = card.getBoundingClientRect();
    const x = ((event.clientX - rect.left) / rect.width) * 100;
    const y = ((event.clientY - rect.top) / rect.height) * 100;
    card.style.setProperty("--spotlight-x", `${x}%`);
    card.style.setProperty("--spotlight-y", `${y}%`);
  });
}

export {};

Implementation recipe

Spotlight Card Implementation Recipe

Goal

Build a card whose border and surface glow follows the cursor, using CSS custom properties and a radial gradient.

Requirements

  • A card element.
  • Pointer tracking inside the card.
  • CSS custom properties for spotlight coordinates.
  • A radial-gradient layer for the glow.
  • Reduced-motion fallback.

Files to create

  • index.html
  • styles.css
  • spotlight.ts

Step 1: Markup

Use an <article> for the card and wrap the content so the gradient layer sits behind it.

Step 2: Define spotlight coordinates

Expose --spotlight-x and --spotlight-y on the card.

.spotlight-card {
  --spotlight-x: 50%;
  --spotlight-y: 50%;
}

Step 3: Create the glow layer

Use a radial gradient as the card background, centered on the custom properties.

background: radial-gradient(
  600px circle at var(--spotlight-x) var(--spotlight-y),
  rgba(139, 92, 246, 0.35),
  transparent 40%
);

Step 4: Add a border highlight

Use a masked pseudo-element to draw a subtle border glow that also follows the cursor.

Step 5: Track the pointer

Convert pointer coordinates to percentages relative to the card.

const x = ((event.clientX - rect.left) / rect.width) * 100;
const y = ((event.clientY - rect.top) / rect.height) * 100;
card.style.setProperty("--spotlight-x", `${x}%`);
card.style.setProperty("--spotlight-y", `${y}%`);

Step 6: Reduced motion

Disable cursor tracking and use a static top-down gradient.

Common pitfalls

  • Reading layout on every pointermove can cause minor overhead; cache the rect when possible.
  • The glow can reduce text contrast if the gradient is too strong.
  • Touch devices do not have hover, so provide a meaningful static state.

Verification checklist

  • Glow follows the cursor smoothly.
  • Border highlight stays aligned with the glow.
  • Text remains readable.
  • Reduced motion shows a static gradient.
  • The card looks acceptable on touch devices.
Accessibility notes
  • The card content remains fully readable without the glow effect.
  • Provide a visible focus state that does not depend solely on the spotlight.
  • Disable cursor tracking under prefers-reduced-motion.
  • Do not use fast pulsing or flashing colors.
Performance notes
  • Update CSS custom properties; do not re-render DOM nodes.
  • Use compositor-only properties; avoid reading layout per frame.
  • Cache the card rect and recalculate only on resize.
  • Use a single background layer for the gradient to minimize paint.
Browser support & fallbacks
API / Feature Support Fallback strategy
Pointer events All modern browsers Static hover state.
CSS custom properties All modern browsers Static gradient.
radial-gradient All modern browsers Solid border or background.