Backgrounds✨ Premium

Gradient Spotlight

A radial CSS mask follows the cursor and reveals a vibrant oklch palette under a muted surface — GPU-composited, zero canvas, zero dependencies.

JSMouseMask

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. Backgrounds has 50 effects, including 6 free. Explore the category →

3 usage examples

Creative agency hero — Brand-reveal background — Gradient Spotlight example 1

① Creative agency hero — Brand-reveal background

WhenA homepage or portfolio landing page shows a centered headline on a large dark background. Visitors naturally start moving the mouse as soon as the page loads.
WhyThe effect turns page exploration into a memorable micro-interaction: each movement reveals a slice of the brand palette without permanently overwhelming the headline with color.
SettingsRadius 120 px (circle 120px), progressive edge (black 0%, transparent 100%), two-color brand palette in oklch, muted surface #0f0f1a.
E-commerce product card — Color variant reveal on hover — Gradient Spotlight example 2

② E-commerce product card — Color variant reveal on hover

WhenA premium e-commerce product card shows a thumbnail on a dark neutral background. The shopper moves the cursor over the card to explore the shades of the selected color variant.
WhyThe spotlight replaces the static swatch gallery: the cursor reveals the oklch shades of the color variant directly on the thumbnail, creating a sensory exploration without switching images — fewer clicks, longer dwell time on the card.
SettingsRadius 60 px (circle 60px), fully progressive edge (black 0%, transparent 100%), monochrome palette centered on the product hue (oklch(.65 .32 30)oklch(.60 .34 355) for a deep bordeaux), muted surface #14100e.
Magazine article header — Editorial mood on dark background — Gradient Spotlight example 3

③ Magazine article header — Editorial mood on dark background

WhenA long-form article or premium feature opens with a full-width dark header showing the title and a tagline. The reader hovers before scrolling down.
WhyThe effect creates a subtle visual tension: color is only visible where the reader looks, reinforcing the sense of exclusivity and premium content without aggressive animation.
SettingsRadius 70 px (circle 70px), warm editorial palette (oklch(.72 .28 55) orange, oklch(.68 .22 40) amber), deep brown muted surface (#1a1208).

How it works

The effect stacks two layers inside .spotlight-container: .spotlight-muted is a dark grey gradient background visible at rest, and .spotlight-vibrant is a multicolor layer positioned absolute inset:0 on top. The vibrant layer is invisible at rest — its mask-image restricts it to a disc centered at the default 50 %, 50 %, effectively hiding it until the pointer moves.

JavaScript listens to mousemove on each [data-spotlight] container and computes the pointer's relative position as a percentage ((clientX - rect.left) / width * 100, same for Y). These values are injected into the CSS custom properties --mx and --my directly on the element. The rule mask-image: radial-gradient(circle 80px at var(--mx, 50%) var(--my, 50%), black 30%, transparent 100%) then reveals the vibrant layer within an 80 px radius around the cursor — black in the mask means opaque, transparent means masked. On mouseleave, the properties reset to 50 % to re-center the disc.

The gradient linear-gradient(135deg, oklch(.7 .3 30), oklch(.6 .28 150), oklch(.65 .32 280), oklch(.7 .25 60)) uses the oklch color space (perceptually uniform lightness). Unlike HSL, where yellow is visibly brighter than blue at the same lightness, oklch keeps perceived luminance constant regardless of hue H — the revealed gradient is balanced without muddy patches or oversaturated spikes.

Rendering is entirely GPU-composited: mask-image triggers layer promotion on the graphics card. The browser updates only two CSS properties per mousemove — no requestAnimationFrame, no pixel reading, no canvas. The transition: background .3s on .spotlight-muted leaves the door open for an animated muted-background change.

Accessibility

  • prefers-reduced-motion absent: the code does not query this media feature. The effect follows the cursor continuously with no reduced-motion guard — add one manually if you target users sensitive to motion.
  • cursor: none hides the native cursor inside the effect area. This can disorient keyboard users or assistive technology users who move the pointer. Remove this rule if accessibility is a priority.
  • The demo .spotlight-label uses color: rgba(255,255,255,.5) on a dark grey background — contrast ratio ≈ 2.5:1, below the WCAG AA threshold of 4.5:1. Switch to color: #e2e8f0 to meet the AA level.
  • No aria-label or role="img" is present in the base markup. Add them if the container carries meaningful content (page background, banner) rather than pure decoration.
  • On touch screens, the spot remains centered at 50 %, 50 % by default — the effect is mouse-only. Add a touchmove listener mirroring the mousemove handler to support mobile devices.

Browser compatibility

Requires CSS mask-image, oklch, and custom properties — supported in all modern browsers since 2022–2023. Zero external dependencies.

Chrome 111+✓ Full
Firefox 113+✓ Full
Safari 15.4+✓ Full
Edge 111+✓ Full
Mobile iOS✓ Spot centered (no native hover)
Android Chrome✓ Spot centered (no native hover)

On a browser without <code>mask-image</code>, the <code>.spotlight-vibrant</code> layer is fully visible (no mask applied) — the vibrant palette shows permanently. On a browser without oklch, gradient color stops are ignored and the layer renders white. In both cases, no fatal JS error.

The code

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

index.html — structure
<div class="spotlight-container" data-spotlight=""
     role="img" aria-label="Interactive color-reveal background — move your mouse">

  <div class="spotlight-muted">
    <!-- Your content (heading, paragraph, button) here.
         Add position:relative; z-index:1 to your elements
         so they remain visible above the vibrant layer. -->
  </div>

  <div class="spotlight-vibrant" aria-hidden="true"></div>
</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
circle 80px 80px Spotlight radius in the mask-image rule of .spotlight-vibrant. Increase (120–200 px) for a wide halo, decrease (40–60 px) for a focused torch.
black 30% 30% Position of the first transparent stop in the mask gradient. At 0%, fully soft edge (smooth fade); at 50%, a well-defined hard disc.
background (.spotlight-vibrant) 4 oklch colors Replace the linear-gradient(135deg, oklch(…) …) of .spotlight-vibrant with your brand palette to adapt the revealed colors to your visual identity.
background (.spotlight-muted) #2a2a3a / #1a1a2a Color of the muted surface at rest. Change to match your page's global background (pure black, deep brown, indigo…).
cursor: none present Set on .spotlight-container. Remove it to keep the native cursor — recommended when accessibility or a custom cursor is a priority.
--mx / --my 50% / 50% CSS properties injected by JS. Assign them manually (el.style.setProperty('--mx','30%')) to position the spot from code, or to drive the spotlight automatically without user interaction.

FAQ

Yes. The script uses querySelectorAll('[data-spotlight]') and attaches an independent mousemove listener to each container. Add as many data-spotlight elements as needed — each manages its own spot without interfering with the others.
oklch guarantees perceptually uniform lightness: two colors at L=0.65 appear equally bright to the eye, regardless of hue H. In HSL, yellow is visibly brighter than blue at the same lightness, creating uneven patches in the revealed gradient. oklch eliminates this problem natively and makes balanced palette creation straightforward.
Add a touchmove listener inside the existing loop: e.addEventListener('touchmove', function(ev){ var t=ev.touches[0]; var r=e.getBoundingClientRect(); e.style.setProperty('--mx',(((t.clientX-r.left)/r.width)*100).toFixed(1)+'%'); e.style.setProperty('--my',(((t.clientY-r.top)/r.height)*100).toFixed(1)+'%'); }, {passive:true}). The spot then follows the finger on touch screens.