npm.io
2.2.0 • Published 2 months ago

react-smart-mouse

Licence
MIT
Version
2.2.0
Deps
0
Size
120 kB
Vulns
0
Weekly
0

react-smart-mouse

Smart custom cursor for React & Next.js — a cursor follower that automatically adapts its shape, size and color to whatever it hovers.

npm version npm downloads CI bundle size license

Unlike a plain cursor follower, SmartMouse knows which element it's hovering — links, buttons, images, inputs, text — and morphs into a different style for each one. Drop it in once and your whole app gets an interactive, animated cursor.

Live Demo

Features

  • Element-aware — automatically restyles itself over a, button, img, input, textarea, checkboxes, radios, and more
  • React 16.14 – 19 and Next.js (Pages & App Router) — ships with "use client", import it anywhere
  • Fast — position updates bypass React re-renders; a single injected style rule hides the native cursor
  • Magnetic hover — the follower snaps to the center of elements you mark as magnetic
  • Smooth trailing — optional lerp smoothing for that lagging-cursor feel
  • Text labels — show "View", "Drag", etc. inside the cursor via a mouselabel attribute
  • Custom cursor content — pass children to replace the dot with anything (emoji, rings, SVG)
  • Glass mode — frosted-glass cursor with blurred backdrop and translucent color tints
  • Image magnifier — the cursor becomes a zoom lens over images marked with mousemagnify
  • Check animation — the cursor pulses when a checkbox or radio is toggled
  • Touch-aware — automatically disables itself on touch-only devices
  • Accessible — respects prefers-reduced-motion out of the box
  • Click feedback — optional press animation via clickScale
  • Fully customizable — per-tag styles, per-class styles, any-CSS-selector styles, per-element mousecustom overrides, blend modes
  • Zero dependencies, ~2 kB min+gzip, TypeScript types included

Installation

npm install react-smart-mouse

Quick start

import SmartMouse from 'react-smart-mouse'

const App = () => (
  <div>
    {/* Your app */}
    <SmartMouse />
  </div>
)

That's it — the native cursor is replaced by an animated follower that reacts to every element it passes over.

Customized
<SmartMouse
  // per-tag styles
  pStyle={{ color: 'rgba(191, 75, 30, 1)', height: '40px', width: '40px' }}
  aStyle={{ color: 'rgba(132, 245, 39, 0.8)', height: '20px', width: '20px' }}
  buttonStyle={{ color: 'rgba(118, 39, 245, 0.8)', height: '20px', width: '20px' }}
  // per-class styles
  classStyle={{
    'some-class': { width: '50px', height: '50px', color: '#996099' },
  }}
  // defaults for everything else
  defaultWidth="35px"
  defaultHeight="35px"
  defaultColor="rgba(0, 0, 0, 0.5)"
  // new in 2.1
  clickScale={0.8}
  blendMode="difference"
  transitionDuration="0.5s"
  lerp={0.15}
  magneticSelector=".btn, .nav-link"
/>

Colors accept HEX, RGB(A), or named colors ('red').

Recipes

Smooth trailing cursor

lerp controls how quickly the follower catches up with the real cursor — 1 sticks to it, lower values trail behind:

<SmartMouse lerp={0.15} />
Magnetic elements

While hovered, magnetic elements pull the follower to their center. Mark them with an attribute or a selector (pairs beautifully with lerp):

<SmartMouse magneticSelector=".magnetic" lerp={0.2} />

<button className="magnetic">Hover me</button>
{/* or per element, no prop needed: */}
<button mousemagnetic="">Hover me</button>
Text labels ("View", "Drag", …)

Show a label inside the cursor while hovering an element — the agency-site classic:

<SmartMouse textStyle={{ fontSize: '14px', fontWeight: 600 }} />

<div className="project-card" mouselabel="View"
     mousecustom={JSON.stringify({ width: '70px', height: '70px' })}>
  ...
</div>
Fully custom cursor

Pass children to replace the built-in dot with anything — the follower still tracks, hides, and click-scales for you:

<SmartMouse clickScale={0.8}>
  <div style={{ width: 24, height: 24, border: '2px solid black', borderRadius: '50%' }} />
</SmartMouse>
Glass cursor

One prop turns the cursor into frosted glass — the backdrop blurs underneath it and all element colors render as translucent tints:

<SmartMouse glass />
Magnify images

Mark any image and the cursor becomes a circular zoom lens while hovering it:

<SmartMouse magnifyZoom={2.5} magnifySize={140} />

<img src="/photo.jpg" data-mousemagnify="" alt="Zoom me" />
Checkbox pulse

Enabled by default — toggling any checkbox or radio makes the cursor pulse. Turn it off with:

<SmartMouse checkAnimation={false} />
Style any element by CSS selector

selectorStyle matches the hovered element (or any ancestor) against arbitrary CSS selectors — a superset of classStyle:

<SmartMouse
  selectorStyle={{
    'nav a':          { width: '20px', height: '20px', color: '#0af' },
    '[data-magnify]': { width: '80px', height: '80px', color: 'rgba(0,0,0,0.2)' },
  }}
/>

TypeScript note: custom JSX attributes like mouselabel may upset strict JSX typing. Every attribute also works with a data- prefix: data-mouselabel, data-mousecustom, data-mousemagnetic, data-mousemagnify.

Next.js

SmartMouse ships with the "use client" directive built into the bundle, so with the App Router you can import it directly in a Server Component such as app/layout.tsx — no wrapper component needed:

// app/layout.tsx
import SmartMouse from 'react-smart-mouse'

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        {children}
        <SmartMouse />
      </body>
    </html>
  )
}

Works with the Pages Router too — just render it in _app.tsx. Tested with Next.js 13 → 16.

Props

prop type default description
pStyle object { color: 'rgba(255, 0, 0, 0.5)', height: '35px', width: '35px', top: '-5px', left: '-10px' } Style over p elements.
aStyle object { color: 'rgba(0, 128, 0, 1)', height: '15px', width: '15px', top: '-5px', left: '-10px' } Style over a elements.
buttonStyle object { color: 'rgba(248, 246, 15, 1)', height: '15px', width: '15px', top: '-5px', left: '-10px' } Style over button elements.
spanStyle object { color: 'rgba(255, 165, 0, 0.5)', height: '35px', width: '35px', top: '-5px', left: '-10px' } Style over span elements.
imgStyle object { color: 'rgba(248, 246, 15, 0.7)', height: '15px', width: '15px', top: '-5px', left: '-10px' } Style over img elements.
inputStyle object { color: 'rgba(67, 39, 245, 1)', height: '30px', width: '2px', top: '-15px', left: '0px' } Style over input elements (text caret look).
textareaStyle object { color: 'rgba(227, 104, 133, 1)', height: '30px', width: '3px', top: '-15px', left: '0px' } Style over textarea elements.
labelStyle object { color: 'rgba(128, 128, 0, 0.5)', height: '35px', width: '35px', top: '-5px', left: '-10px' } Style over label elements.
liStyle object { color: 'rgba(204, 147, 198, 0.5)', height: '35px', width: '35px', top: '-5px', left: '-10px' } Style over li elements.
checkboxStyle object { color: 'rgba(129, 242, 63, 1)', height: '10px', width: '10px', top: '-5px', left: '-5px' } Style over checkboxes.
radioButtonStyle object { color: 'rgba(150, 63, 242, 1)', height: '10px', width: '10px', top: '-5px', left: '-5px' } Style over radio buttons.
rangeStyle object { color: 'rgba(67, 39, 245, 0.8)', height: '18px', width: '18px', top: '-9px', left: '-9px' } Style over range sliders. New in 2.2
classStyle object none Styles keyed by className: { [className]: ElementStyle }.
selectorStyle object none Styles keyed by any CSS selector, matched with closest(). New in 2.1
defaultWidth string 35px Default follower width.
defaultHeight string 35px Default follower height.
defaultTop number | string -5 Default top offset of the follower.
defaultLeft number | string -10 Default left offset of the follower.
defaultColor string rgba(0, 0, 0, 0.5) Default follower color.
zIndex number 3000 z-index of the follower. New in 2.1
transitionDuration string 0.7s CSS duration for size/color morphs. New in 2.1
clickScale number 1 Scale applied while the mouse button is pressed, e.g. 0.8. New in 2.1
blendMode string none CSS mix-blend-mode for the follower, e.g. 'difference'. New in 2.1
hideOnLeave boolean true Hide the follower when the cursor leaves the window. New in 2.1
lerp number 1 Smoothing factor in (0, 1]. Lower = more trailing lag, e.g. 0.15. New in 2.1
magneticSelector string none CSS selector for magnetic elements — follower snaps to their center. New in 2.1
textStyle object { fontSize: '12px', color: '#fff' } Style for mouselabel text (any CSSProperties). New in 2.1
respectReducedMotion boolean true Disable animations when the OS requests reduced motion. New in 2.1
checkAnimation boolean true Pulse the cursor when a checkbox/radio is toggled. New in 2.2
glass boolean false Frosted-glass cursor: blurred backdrop, translucent colors. New in 2.2
magnifyZoom number 2 Zoom factor for mousemagnify images. New in 2.2
magnifySize number 120 Lens diameter (px) for the image magnifier. New in 2.2
children ReactNode none Custom cursor content rendered instead of the built-in dot. New in 2.1
debug boolean false Keep the native cursor visible and log hovered elements to the console.
interface ElementStyle {
  width?: string
  height?: string
  color?: string
  top?: number | string
  left?: number | string
}

Style priority

When several rules could apply to the hovered element, the follower resolves them in this order:

  1. mousecustom attribute on the element (or an ancestor)
  2. selectorStyle match on the element or an ancestor
  3. classStyle match on the element's className
  4. Per-tag style (buttonStyle, aStyle, …)
  5. Defaults (defaultWidth, defaultColor, …)

Per-element overrides with mousecustom

Give any single element its own cursor style with the mousecustom attribute (also matches when hovering the element's children, and works as data-mousecustom):

<input type="text" mousecustom={JSON.stringify({ height: '50px', color: '#e1e89e' })} />

Touch devices & accessibility

On devices without a fine pointer (phones, tablets) the component renders nothing and leaves the native behavior untouched — no configuration needed.

When the OS reports prefers-reduced-motion: reduce, all transitions and trailing are disabled while the cursor stays fully functional. Opt out with respectReducedMotion={false}.

TypeScript

Full types are shipped with the package:

import SmartMouse, { SmartMouseProps, ElementStyle } from 'react-smart-mouse'

License

MIT Vadym Bilan. See LICENSE.

Keywords