Spotlight Effect
A ::before pseudo-element tracked by the cursor via two CSS properties creates a glowing halo on the card — radial gradient, opacity transition, zero canvas.
Updated
This effect is part of Effect.Labs — 840 vanilla effects, some free, some premium. Cards has 43 effects, including 6 free. Explore the category →
3 usage examples



How it works
The core is the .card-spotlight::before pseudo-element, 200 × 200 px, positioned absolute. Its left: var(--spotlight-x, 50%) and top: var(--spotlight-y, 50%) paired with transform: translate(-50%, -50%) center it exactly on the cursor. Its fill is a radial gradient: radial-gradient(circle, rgba(99,102,241,.5) 0, transparent 70%) — saturated indigo at center, fully transparent at 70% of the radius. The card carries overflow: hidden to clip the halo at its edges.
Position updates are synchronous and direct: on every mousemove event, JS calls getBoundingClientRect() on the card to convert viewport coordinates (clientX, clientY) into card-relative coordinates, then pushes them with style.setProperty('--spotlight-x', …) and style.setProperty('--spotlight-y', …). No requestAnimationFrame needed — the update tracks the cursor pixel by pixel with no lag.
Appearance and disappearance are handled by :hover and an opacity transition: opacity: 0 by default, opacity: 1 on .card-spotlight:hover::before, with transition: opacity .3s. Without this fade, the spot would snap into view abruptly on pointer entry even if the CSS variables are already at the right position.
Accessibility
- prefers-reduced-motion not handled in this code — the 0.3 s opacity transition plays regardless of system preferences. Recommended fix:
@media (prefers-reduced-motion: reduce) { .card-spotlight::before { transition: none; } }. - The effect is purely decorative: it neither modifies content nor the semantic structure of the card — no impact on screen readers.
:hover-only activation, not:focus. For keyboard users, add.card-spotlight:focus-within::before { opacity: 1; }.- Content contrast: background
#12121awith white text — ratio above 10:1, meets WCAG AA and AAA. - No continuous motion: no RAF, no looping CSS animation — the effect is only active during actual cursor movements.
Browser compatibility
Requires CSS custom properties (var()) and pseudo-elements — available in all modern browsers. Zero external dependencies.
Without CSS custom property support, the spot stays fixed at the card center (fallback value 50% declared in CSS) — a static highlight, no JS errors.
The code
HTML structure to paste into your page (the CSS + JS come with Premium):
<div class="card-demo card-spotlight" id="spotlightCard">
<h3 class="card-title">Title</h3>
<p class="card-body">Card content</p>
</div>
The full HTML + CSS + JS of Spotlight Effect, ready to paste — plus all 840 effects. Or $299 once, lifetime access.
Customize
Options passed to the API or data-* attributes:
| Option / property | Default | Effect |
|---|---|---|
| Spot color | rgba(99, 102, 241, .5) | First value in radial-gradient(circle, COLOR 0, transparent 70%) on .card-spotlight::before. All CSS color formats accepted (rgba, hsl, hex). |
| Spot size (width / height) | 200px | Dimensions of the ::before element. Increase (300–400 px) for a diffuse halo on large cards; decrease (120 px) for a tighter beam. |
| Transparency falloff | 70% | Second parameter of radial-gradient(circle, … 0, transparent 70%). Lower (50%) = harder edge; higher (85%) = very soft halo. |
| Transition duration | 0.3s | The transition: opacity .3s property on .card-spotlight::before. 0 s = instant; 0.8 s = slow cinematic fade. |
| --bg-card | #12121a | CSS variable for card background. Override inline: style="--bg-card: #1e1b4b". |
| --radius-xl | 1rem | Corner radius applied via border-radius: var(--radius-xl) on the card. |
FAQ
mousemove event and :hover stays inactive: the spot remains hidden. For a touch version, listen to touchmove alongside and call the same style.setProperty() with touch coordinates.getElementById('spotlightCard') — one element only. For a grid, replace with document.querySelectorAll('.card-spotlight') and loop: els.forEach(el => el.addEventListener('mousemove', e => { const r = el.getBoundingClientRect(); el.style.setProperty('--spotlight-x', (e.clientX-r.left)+'px'); el.style.setProperty('--spotlight-y', (e.clientY-r.top)+'px'); }));--spot-color: rgba(99,102,241,.5) in .card-spotlight::before and use it in the gradient, then override per state: .card-spotlight.is-danger { --spot-color: rgba(239,68,68,.5); } — color changes without touching the JS.