Cursor effect

Cursor spotlight effect: a glow that follows the mouse

A soft glow follows the pointer across a dark panel, as if you were holding a torch over it. The text sits above the light, so it stays readable.

The effect running on this page, with exactly the code below. Move your mouse inside the stage: the effect stays inside it. On a touch screen, or with reduced motion turned on, it holds back, as it should.

Usage

When to use it

  • Dark hero sections and feature panels on product pages.
  • Pricing or feature cards, to light up the one under the pointer.
  • Teaser pages where a little discovery fits the mood.
  • Keep body text above the glow layer, as this snippet does, and check contrast with the glow at its brightest.

Code

Copy the effect

HTML
<section class="spotlight">
  <h2>Move your mouse over this panel</h2>
  <p>A soft light follows the pointer. The text stays above it.</p>
</section>
CSS
.spotlight {
  --spot-x: 50%;
  --spot-y: 50%;
  position: relative;
  overflow: hidden;
  padding: 64px 32px;
  background: #15161a;
  color: #f5f3ec;
}
.spotlight::before {
  content: "";
  position: absolute;
  inset: 0;
  background: radial-gradient(280px circle at var(--spot-x) var(--spot-y), rgba(45, 91, 255, 0.35), transparent 70%);
  opacity: 0;
  transition: opacity 0.3s ease;
  pointer-events: none;
}
.spotlight > * {
  position: relative;
}
.spotlight.is-lit::before {
  opacity: 1;
}
@media (prefers-reduced-motion: reduce) {
  .spotlight::before {
    --spot-x: 50%;
    --spot-y: 50%;
    opacity: 1;
    transition: none;
  }
}
JavaScript
(() => {
  const reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)');

  document.querySelectorAll('.spotlight').forEach((panel) => {
    let frame = 0;
    let x = 0;
    let y = 0;

    panel.addEventListener('pointermove', (event) => {
      if (reduceMotion.matches || event.pointerType === 'touch') return;
      const rect = panel.getBoundingClientRect();
      x = event.clientX - rect.left;
      y = event.clientY - rect.top;
      panel.classList.add('is-lit');
      if (frame) return;
      frame = requestAnimationFrame(() => {
        panel.style.setProperty('--spot-x', `${x}px`);
        panel.style.setProperty('--spot-y', `${y}px`);
        frame = 0;
      });
    });

    panel.addEventListener('pointerleave', () => panel.classList.remove('is-lit'));
  });
})();

Accessibility

Motion, touch and screen readers

Reduced motion

With prefers-reduced-motion: reduce, the CSS pins the glow to the centre of the panel and shows it at full strength with no fade, and the script stops updating the position. Visitors get a still, evenly lit panel instead of a moving light.

Accessibility notes

  • The glow is a CSS background on a pseudo-element, so screen readers never see it and it cannot block clicks.
  • Text and buttons sit above the glow (position: relative on the panel's children). Check contrast at the glow's brightest point.
  • The native cursor is untouched.
  • Touch screens never see the moving light; the panel simply stays dark until it is hovered with a mouse.

Site builders

Webflow, Framer and React

Webflow

In Webflow, put the CSS inside a <style> tag and the JavaScript inside a <script> tag in the page's custom code (Before </body> tag), or site-wide in Site settings > Custom code. Give your section the class spotlight in the Designer. The ::before glow lives in the CSS, so no extra elements are needed.

Framer

Add the class through an Embed wrapper, or rebuild the panel as a code component that renders the section and runs the script in useEffect. Framer's built-in effects do not expose the pointer position as a CSS variable.

React

Use a ref on the section and attach the pointermove and pointerleave listeners in useEffect, or use React's onPointerMove prop and set the CSS variables with ref.current.style.setProperty. Avoid storing x and y in state; updating a CSS variable skips a re-render.

FAQ

Questions

Can I use the spotlight on light backgrounds?
Yes, but a light glow on a light panel is hard to see. Use a darker or more saturated colour, or switch the gradient to darken instead of lighten.
How do I make the light bigger?
Change 280px in the radial-gradient to the radius you want.
Can several panels each have their own spotlight?
Yes. The script sets up every element with the class spotlight, and each keeps its own position.