Cursors✨ Premium

Cursor Spotlight

A 95% black veil with a 120 px hole that follows the pointer, laid over content kept at 15% opacity at rest and raised to 100% on hover: the text is readable only under the spotlight. The native cursor stays visible — this is lighting, not a replacement cursor.

JSSpotlight

You'll get access to the interactive demo with a free account.

This effect is part of Effect.Labs — 811 vanilla effects, some free, some premium. Cursors has 26 effects, including 1 free. Explore the category →

3 usage examples

“Coming soon” card — specs in the shadows — Cursor Spotlight example 1

① “Coming soon” card — specs in the shadows

WhenPre-launch section of a product: name, date, three specs discovered by sweeping across.
WhyThe veil embodies the secret without making it frustrating: everything is there, you just move the mouse. The 120 px fade gives a torchlight look that fits an object being unveiled.
Settings160 px radius to light a whole spec line, veil rgba(0,0,0,.92), resting opacity .15 kept.
Night visit — an artist's quote on black — Cursor Spotlight example 2

② Night visit — an artist's quote on black

WhenExhibition or museum page: a quote, its author, the date. Nothing else to read.
WhyHere the spotlight is literal: lighting a work in a dark room. The content is short, held within the circle, and reading it isn't required to navigate.
Settings120 px radius (default), resting opacity lowered to .1 for a deeper black, .5s transition for a slow light-up.
“Under the hood” section — privacy arguments — Cursor Spotlight example 3

③ “Under the hood” section — privacy arguments

WhenLanding page of a messaging or storage app: three technical guarantees presented like a vault.
WhyThe metaphor of light on what is hidden serves the message; the raised resting opacity keeps the headings readable without hover, so the argument stays accessible.
SettingsResting opacity .3, 140 px radius, veil .9, @media (hover: none) removing the overlay on mobile.

How it works

Three layers inside the #zone-spotlight-cursor area (.spotlight-cursor-zone, 300 px tall, #0f0f1a background, overflow: hidden). Underneath, .spotlight-content: position: absolute; inset: 0, centered flex column, opacity: .15 and transition: opacity .3s. On top, #spotlightOverlay (.spotlight-cursor-overlay): inset: 0, pointer-events: none and a radial-gradient(circle 120px at var(--spot-x, 50%) var(--spot-y, 50%), transparent 0%, rgba(0,0,0,.95) 100%). The .info-text, declared after the overlay in the DOM, stays above the veil.

The JS is a single mousemove listener on the zone: x = (clientX − rect.left) / rect.width × 100, same for y, then spotlightOverlay.style.setProperty('--spot-x', x + '%'). Coordinates are percentages of the zone, so the gradient center follows the pointer whatever the container's size (measured: --spot-x: 25%, --spot-y: 50% for a pointer at a quarter of the width). No requestAnimationFrame, no smoothing: the hole is glued to the pointer, and nothing runs outside hover (0 rAF calls/s measured).

The gradient goes from transparent at the center to rgba(0,0,0,.95) at 120 px: not a sharp disc but a fade over a 120 px radius, after which the veil is a uniform 95%. Content is lit only inside a circle of about 240 px across, and CSS alone makes it readable: .spotlight-cursor-zone:hover .spotlight-content { opacity: 1 }, with the 300 ms transition. On leaving, :hover drops and the content falls back to 15%.

No mouseleave listener: the --spot-x/--spot-y variables keep their last value (measured: still 25%/50% after leaving), so on the next entry the hole sits where it was left until the first mousemove. No cursor: none: the system arrow remains. Two details in the shipped HTML: the h3 carries an inline style="font-family: Inter, …" (system fallback if Inter isn't loaded) and keeps its browser margins (20 px measured, not reset).

Accessibility

  • Deliberately zero contrast at rest: the white title at 15% opacity on #0f0f1a gives 1.5:1, the paragraph (rgba(255,255,255,.7)) 1.3:1, and under the veil outside the circle 1.07:1. On hover, under the spotlight: 19:1 and 9.5:1. The content must therefore carry nothing essential, or be repeated elsewhere; otherwise raise the resting opacity to .5 (5.3:1 for white).
  • No hover, no reading: on keyboard and touch, :hover doesn't reliably stick and the content stays at 15%. Add @media (hover: none) { .spotlight-content { opacity: 1 } .spotlight-cursor-overlay { display: none } } and, if the zone holds a focusable element, .spotlight-cursor-zone:focus-within .spotlight-content { opacity: 1 }.
  • prefers-reduced-motion: the only autonomous motion is the 300 ms opacity transition; the hole follows the hand, it isn't animated. Nothing mandatory, but @media (prefers-reduced-motion: reduce) { .spotlight-content { transition: none } } skips the fade for those who asked.
  • Screen readers: the overlay is an empty div, the content is ordinary DOM announced normally whatever its visibility — the right situation. The .info-text (“Spotlight effect”) is read too: remove it or set it aria-hidden outside the demo.
  • The content is position: absolute; inset: 0 in a fixed-height zone (300 px) with overflow: hidden: text longer than the zone is cut off with no scrolling. Plan a min-height or short content; at 200% zoom, check that nothing disappears.

Browser compatibility

Requires CSS custom properties with setProperty, the inset shorthand and radial-gradient() with explicit size and position. ES2015 JS (arrow functions, const). Zero dependencies.

Chrome 87+✓ Full
Firefox 66+✓ Full
Safari 14.1+✓ Full
Edge 87+✓ Full
Mobile iOS✓ Content stuck at 15% (no hover)
Android Chrome✓ Content stuck at 15% (no hover)

Without JavaScript, the hole stays centered (50%/50% fallbacks) and hover still lights the content: a static, usable vignette. Without inset (Safari before 14.1, Firefox before 66), content and overlay lose their size: no more veil, the content shows at 15% then 100% on hover.

The code

HTML structure to paste into your page (CSS + JS available with a premium account):

index.html — structure
<div class="cursor-zone spotlight-cursor-zone" id="zone-spotlight-cursor">
  <div class="spotlight-content">
    <h3 style="font-family: Inter, -apple-system, BlinkMacSystemFont, sans-serif;">Lit content</h3>
    <p>The cursor lights the content</p>
  </div>
  <div class="spotlight-cursor-overlay" id="spotlightOverlay"></div>
  <span class="info-text" style="color: rgba(255,255,255,0.5);">Spotlight effect</span>
</div>
🔒 Unlock the full code — from €2.99 the first month

Full HTML + CSS + JS, copy-paste ready — with hundreds of premium effects.

Customize

Options passed to the API or data-* attributes:

Option / propertyDefaultEffect
Hole radius (CSS — circle 120px) 120 px Distance over which the veil goes from transparent to 95%. 80 px = narrow flashlight; 200 px = wide halo that reveals a whole block.
Veil density (CSS — rgba(0,0,0,.95)) .95 Opacity of the black outside the circle. .8 lets the content show through; 1 hides it completely (the pointer is then the only window).
Resting opacity (CSS — .spotlight-content opacity) .15 Content visibility without hover. This is the accessibility knob: .5 gives 5.3:1 on white, 1 keeps everything readable and leaves only the veil.
Transition (CSS — .spotlight-content) opacity .3s Duration of the content lighting up and dimming on hover. The hole itself is never animated (variables aren't interpolated).
Gradient profile (CSS — 0% / 100% stops) transparent 0% → black 100% transparent 45%, rgba(0,0,0,.95) 100% creates a clear disc with a short soft edge; two identical stops give a hard edge.
Zone background (CSS — .spotlight-cursor-zone background) #0f0f1a Color seen through the veil and under the hole. A light background breaks the effect: keep a dark shade, or raise the veil to 1.
Reset on leave (JS — mouseleave listener to add) absent setProperty('--spot-x', '50%') and '--spot-y', '50%' in a mouseleave re-center the hole instead of leaving it at the last hovered point.

FAQ

Yes, that is the shipped behavior and it stacks two things: the content at opacity: .15 and the 95% veil on top. Measured: 1.5:1 for the title at rest, 1.07:1 under the veil. If you want readable content with a simple lamp effect on hover, raise the resting opacity to 1 and keep the overlay: the spotlight becomes ambient lighting and the text stays compliant.
Not with a regular transition: --spot-x is an untyped variable, the browser can't interpolate between two percentages and jumps from one value to the other. Two routes: register the variable with @property --spot-x { syntax: '<percentage>'; inherits: false; initial-value: 50% } then transition: --spot-x .15s, --spot-y .15s on the overlay (Chrome 85+, Safari 16.4+, Firefox 128+); or smooth in JS with a requestAnimationFrame loop and a factor, like Dot Follower.
The code has no mouseleave listener: the variables keep their last value (measured 25%/50% after leaving) and the next hover reuses them until the first movement. Add zone.addEventListener('mouseleave', () => { overlay.style.setProperty('--spot-x', '50%'); overlay.style.setProperty('--spot-y', '50%'); }). The content does fall back to 15%, since :hover is handled by CSS.