Skip to content
38

Cursor Glow

Ambient glow layer that follows the pointer across its container and fades when it leaves. Drop it behind heroes, grids or feature panels.

Example

Installation

npx shadcn@latest add https://hirael.com/r/cursor-glow.json

Usage

import { CursorGlow } from "@/components/cursor-glow"

Paths assume the default @/ alias from your components.json. The example above shows the parts composed.

API

<CursorGlow />

+ native element props
PropTypeDefault
size

Diameter of the glow, in px.

number400
color

Glow color. Defaults to a soft tint of the foreground token.

string'color-mix(in oklch, var(--foreground) 12%, transparent)'
blur

Soften the glow's edge. Turn off for a crisper spotlight.

booleantrue

Component source

'use client';

import * as React from 'react';
import { motion, useMotionTemplate, useMotionValue, useReducedMotion } from 'motion/react';

import { cn } from '@/lib/utils';

export interface CursorGlowProps extends React.ComponentProps<'div'> {
  /** Diameter of the glow, in px. */
  size?: number;
  /** Glow color. Defaults to a soft tint of the foreground token. */
  color?: string;
  /** Soften the glow's edge. Turn off for a crisper spotlight. */
  blur?: boolean;
}

const CursorGlow = ({
  className,
  children,
  size = 400,
  color = 'color-mix(in oklch, var(--foreground) 12%, transparent)',
  blur = true,
  onPointerMove: onPointerMoveProp,
  ...props
}: CursorGlowProps) => {
  const reduced = useReducedMotion();
  const x = useMotionValue(0);
  const y = useMotionValue(0);
  const background = useMotionTemplate`radial-gradient(${size}px circle at ${x}px ${y}px, ${color}, transparent 70%)`;

  const onPointerMove = (event: React.PointerEvent<HTMLDivElement>) => {
    onPointerMoveProp?.(event);
    if (event.defaultPrevented || reduced || event.pointerType !== 'mouse') return;
    const rect = event.currentTarget.getBoundingClientRect();
    x.set(event.clientX - rect.left);
    y.set(event.clientY - rect.top);
  };

  return (
    <div
      data-slot="cursor-glow"
      className={cn('group relative overflow-hidden', className)}
      {...props}
      onPointerMove={onPointerMove}
    >
      {/* The layer never tracks the pointer under reduced motion, so it would sit pinned at 0,0. */}
      {reduced ? null : (
        <motion.div
          aria-hidden
          data-slot="cursor-glow-layer"
          className={cn(
            'pointer-events-none absolute inset-0 opacity-0 transition-opacity duration-500 group-hover:opacity-100',
            blur && 'blur-2xl',
          )}
          style={{ background }}
        />
      )}
      <div data-slot="cursor-glow-content" className="relative">
        {children}
      </div>
    </div>
  );
};

export { CursorGlow };

Dependencies

npm

motion