EmpireUI
Sign inGet Pro
← BlogEnglish · 6 minspotlight effectreacttailwind css

How to Add a Spotlight Effect in React + Tailwind (Free)

Learn how to build a stunning spotlight effect in React and Tailwind CSS from scratch — completely free, no paid libraries required.

What Is a Spotlight Effect and Why Use It?

A spotlight effect is a dynamic lighting trick where a radial gradient follows the user's cursor, illuminating a specific area of a card, hero section, or background — as if a real spotlight is tracking the pointer. It gives interfaces a cinematic, high-end feel without requiring WebGL or heavy animation libraries.

The technique has become popular in modern SaaS landing pages and component libraries because it creates perceived depth and interactivity with very little JavaScript. Users instinctively engage with elements that react to their presence, making spotlight effects a powerful conversion tool.

On Empire UI you can find a ready-made Spotlight Card component as part of the full component library, or you can follow this guide to build your own from scratch and understand exactly how it works.

How the Spotlight Effect Works Under the Hood

The core mechanic is simple: on every mousemove event you record the cursor position relative to the target element, then inject those coordinates as CSS custom properties (--x and --y). A radial-gradient background reads those variables and paints a soft circle of light wherever the cursor is.

The gradient is placed on a pseudo-element or an overlay <div> that sits on top of the card content via pointer-events: none, so it never blocks clicks. Tailwind's [background:...] arbitrary-value syntax makes it trivial to wire the gradient to CSS variables without leaving your JSX.

Understanding this pattern also unlocks related effects — you can combine it with glassmorphism blur layers, or use the same cursor-tracking logic to drive custom cursors and border glow animations like the Moving Border component.

Building a Spotlight Card Component Step by Step

Start by creating a SpotlightCard.tsx file. The component wraps any children, attaches a ref to the outer <div>, and uses a useRef to store the overlay element. On mousemove it calculates offsets with getBoundingClientRect() and writes them to CSS variables: ``tsx import { useRef, MouseEvent } from 'react'; export function SpotlightCard({ children }: { children: React.ReactNode }) { const containerRef = useRef<HTMLDivElement>(null); const overlayRef = useRef<HTMLDivElement>(null); const handleMouseMove = (e: MouseEvent<HTMLDivElement>) => { if (!containerRef.current || !overlayRef.current) return; const { left, top } = containerRef.current.getBoundingClientRect(); const x = e.clientX - left; const y = e.clientY - top; overlayRef.current.style.setProperty('--x', ${x}px); overlayRef.current.style.setProperty('--y', ${y}px); }; return ( <div ref={containerRef} onMouseMove={handleMouseMove} className="relative overflow-hidden rounded-2xl border border-white/10 bg-neutral-900 p-6" > {/* Spotlight overlay */} <div ref={overlayRef} className="pointer-events-none absolute inset-0 opacity-0 transition-opacity duration-300 group-hover:opacity-100" style={{ background: 'radial-gradient(600px circle at var(--x) var(--y), rgba(120,119,198,0.15), transparent 40%)', }} /> {children} </div> ); } ``

Notice the pointer-events-none class on the overlay — this ensures mouse events pass through to the card's interactive children. The transition-opacity combined with a Tailwind group-hover:opacity-100 creates a smooth fade-in when the user enters the card area.

Adjust the 600px circle radius and the rgba colour to match your design system. A warm amber (rgba(251,191,36,0.2)) suits neobrutalist themes, while a cool rgba(59,130,246,0.15) blue fits glassmorphism cards — check the glassmorphism guide for pairing ideas.

Making the Effect Work on a Full-Page Hero

For a hero-section spotlight that tracks the cursor across the entire viewport, move the event listener to window inside a useEffect. Store the mouse position in a useRef (not useState) to avoid re-renders on every pixel moved: ``tsx import { useEffect, useRef } from 'react'; export function SpotlightHero() { const spotRef = useRef<HTMLDivElement>(null); useEffect(() => { const move = (e: globalThis.MouseEvent) => { if (!spotRef.current) return; spotRef.current.style.setProperty('--mx', ${e.clientX}px); spotRef.current.style.setProperty('--my', ${e.clientY}px); }; window.addEventListener('mousemove', move); return () => window.removeEventListener('mousemove', move); }, []); return ( <section className="relative min-h-screen bg-black overflow-hidden"> <div ref={spotRef} className="pointer-events-none fixed inset-0" style={{ background: 'radial-gradient(800px circle at var(--mx) var(--my), rgba(99,102,241,0.12), transparent 50%)', }} /> <div className="relative z-10 flex items-center justify-center min-h-screen"> <h1 className="text-6xl font-bold text-white">Hello, spotlight.</h1> </div> </section> ); } ``

Using position: fixed on the overlay means it stays locked to the viewport coordinate system — perfect when the hero spans the full screen. If your page scrolls, switch to position: absolute and use pageX/pageY instead of clientX/clientY.

For performance-sensitive apps you can further optimise by debouncing the handler with requestAnimationFrame, ensuring the DOM write happens at most once per frame. Pair this hero with an Aurora Background or Shooting Stars for a layered atmospheric look.

Accessibility and Mobile Considerations

Spotlight effects are purely decorative and should never carry meaning. Always wrap the visual overlay in aria-hidden="true" and never rely on hover state to reveal critical content — the effect must degrade gracefully on touch devices where mousemove does not fire.

On mobile you can either disable the effect entirely with a @media (pointer: coarse) CSS check, or substitute a subtle entrance animation (a single centered glow that fades in on mount) so touch users still get visual polish without the tracking logic.

Test your component with prefers-reduced-motion: wrap the overlay opacity transition in a check and skip it when the user has requested reduced motion. This keeps Empire UI components inclusive by default — the same philosophy behind every component in the templates library.

Going Further with Empire UI's Pre-Built Spotlight Components

If you want the spotlight effect without writing it from scratch, Empire UI ships a fully-accessible SpotlightCard and SpotlightButton ready to drop into any project — no attribution required, MIT licensed. Browse them in the component tools section alongside 40+ visual style variants.

The MCP server lets you generate a customised spotlight component with a single natural-language prompt straight into your IDE: just describe the colour, radius, and animation timing and the server scaffolds the TypeScript + Tailwind code for you. It's the fastest path from idea to production-ready component.

For maximum visual impact, try composing the spotlight effect with other Empire UI primitives: a Floating Navbar that glows on hover, Animated Tabs that light up on focus, or a full Bento Grid layout where each cell has its own independent spotlight. The compound effect is striking and entirely free.

FAQ

Does the spotlight effect hurt performance?

Not if implemented correctly. Writing two CSS custom properties on mousemove is a cheap DOM operation. For extra safety, wrap the handler in requestAnimationFrame so the gradient repaints at most once per frame, keeping the effect at a smooth 60 fps even on low-end devices.

Can I use the spotlight effect with Tailwind CSS only — no inline styles?

Partially. You can define the gradient in a Tailwind arbitrary-value class like [background:radial-gradient(...)], but CSS custom properties updated from JavaScript still require either inline styles or a CSS variable on the element. The hybrid approach shown in this guide (Tailwind for layout, one inline style for the gradient) is the standard pattern.

Does the spotlight effect work inside a scrollable container?

Yes. Use getBoundingClientRect() to get the element's viewport-relative position and subtract it from clientX/clientY. If the container itself scrolls, also add container.scrollTop and container.scrollLeft to the offsets so the gradient tracks accurately after scrolling.

Is the Empire UI spotlight component free for commercial projects?

Yes. All Empire UI components are MIT licensed, meaning you can use them in personal and commercial projects with no restrictions. No subscription, no attribution required, and no premium tier gating the spotlight component.

How do I change the spotlight colour to match my brand?

Replace the rgba value in the radial-gradient string. For a green brand accent try rgba(34,197,94,0.2), for purple use rgba(168,85,247,0.2). You can even animate between two colours using a CSS @keyframes on the overlay's opacity for a pulsing brand spotlight.

Free components in 40 styles
React & Tailwind, copy-paste ready.
Browse →

Read next

How to Create an Aurora Background in React (Free Tailwind)How to Create a Moving Border Effect in React + TailwindHow to Add a Custom Cursor in React (Free Tailwind Guide)How to Add a Shooting Stars Background in React (Free)