Retour au catalogue

Hero Cursor Mask

Hero à double couche avec effet masque curseur : une version dark et une version light superposées. La version light est révélée via un clipPath circle animé qui suit la souris (useSpring). Inspiré de la technique Olivier Larose. Effet wow garanti au premier survol.

herocomplex Both Responsive a11y
boldeleganteditorialagencyportfoliosaascentered
Theme

How to build a cursor-reveal hero section in React

A cursor-reveal hero in React stacks two layers and clips the top one to a circular clip-path that follows the pointer via a Framer Motion spring, revealing the layer beneath. Build it with useMotionValue for the position and useSpring for the radius.

  • Stack: React + Framer Motion + Tailwind v4, ~290 lines, zero extra dependencies.
  • Core API: useMotionValue, useSpring, useMotionTemplate, clip-path.
  • Accessible: both layers carry the same readable text; the mask is decorative.
  • Needs a static fallback on touch devices (no pointer).

Hero Cursor Mask is a full-screen React hero where moving the cursor reveals a second, lighter layer through a spring-animated circular window. It turns a static headline into a tactile, explore-me surface, the kind of detail that makes a landing page feel handcrafted rather than templated.

Anatomy

Two identical content layers are stacked absolutely: a dark base layer (background + dot grid + radial vignette) and a light layer on top. The light layer is hidden everywhere except inside a circular clip-path that tracks the pointer. A 12px accent dot acts as a custom cursor, blended with mix-blend-difference so it stays visible over both layers.

How it works

The effect rides on Framer Motion's useMotionValue + useSpring. Pointer coordinates feed two motion values (mouseX/mouseY); the circle radius is its own spring (stiffness 200, damping 28) that expands from 0 to 180px on enter and collapses on leave. useMotionTemplate stitches them into a live `circle(${size}px at ${x}px ${y}px)` clip-path string, so the reveal follows the cursor with natural inertia instead of snapping.

How to build it in React

  1. Stack two full-screen layers

    Render the same content twice inside a relative container with overflow:hidden. The bottom layer uses your dark theme, the top layer the light one. Set the container cursor to none, you'll draw your own.

  2. Track the pointer with motion values

    On mousemove, convert clientX/Y to container-relative coordinates with getBoundingClientRect and write them to mouseX/mouseY motion values. Keep a separate spring for the radius so it eases in and out.

    const mouseX = useMotionValue(0);
    const size = useSpring(rawSize, { stiffness: 200, damping: 28 });
    const clipPath = useMotionTemplate`circle(${size}px at ${mouseX}px ${mouseY}px)`;
  3. Clip the top layer

    Apply the motion clipPath (and -webkit-clip-path) to the light layer. Add a small motion.div positioned at mouseX/mouseY with mixBlendMode:'difference' as the custom cursor.

When to use it

Reach for it on a brand/product landing hero where you want one memorable interaction above the fold, agencies, design tools, AI products. Avoid it on content-dense or conversion-critical pages where a clear CTA matters more than delight, and provide a static fallback for touch devices (there is no cursor to follow).

Used by

  • Linear, Cursor-reactive surfaces and spotlight reveals across its marketing site.
  • Vercel, Pointer-driven gradients and masked highlights in hero sections.
  • Family, Playful cursor-led interactions as a core brand signature.

FAQ

Does the cursor mask work on mobile?

No, there is no pointer on touch screens, so ship a static version of the light or dark layer as the mobile fallback and skip the mousemove listeners.

Why use a spring instead of a CSS transition?

The spring gives the reveal weight and inertia so it trails the cursor naturally; a linear CSS transition feels mechanical and lags uniformly regardless of speed.

Is it accessible?

Both layers contain the same readable text, so screen readers and no-JS users still get the full content; the mask is purely decorative and the dark layer stands on its own.

"use client";

import {
  motion,
  useMotionTemplate,
  useMotionValue,
  useSpring,
} from "framer-motion";
import { ArrowRight } from "lucide-react";
import { useCallback, useRef } from "react";

interface HeroCursorMaskProps {
  badge?: string;
  title?: string;
  titleAccent?: string;
  description?: string;
  ctaLabel?: string;
  ctaUrl?: string;
}

const EASE = [0.16, 1, 0.3, 1] as const;
const CIRCLE_EXPANDED = 180;

Code complet réservé à Pro

Code source intégral, export multi-framework et playground.

Passer en Pro, 9,99€/mois

Reviews

React Cursor-Reveal Hero (Clip-Path Mask), Code + Tutorial