Library / SDK
Cuberto/mouse-follower avatar
Cuberto/mouse-follower

mouse-follower replaces the browser cursor with a DOM element you can style

A powerful javascript library to create amazing and smooth effects for the mouse cursor on your website.

821 stars69 forksJavaScriptMIT

At a glance

What is it?
Cuberto's mouse-follower is a small JavaScript library that hides the native cursor and draws its own as a real element in the page, with states for links, text, icons and media, and smooth movement driven by GSAP. It is MIT licensed, at version 1.2.1, and it needs GSAP registered before it will move.
Who is it for?
mouse-follower fits a marketing or portfolio site that wants a cursor that reacts to what it is over, because the states, the data attribute and the class names are all exposed as options rather than hard coded, and the movement, easing and skew are handled by GSAP.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 55 days ago.
What is it written in?
Mainly JavaScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 25, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A cursor you can put a class on

mouse-follower is a JavaScript library for creating smooth effects for the mouse cursor on a website, by Cuberto. The version in the package manifest is 1.2.1, published on 2026-08-31, following v1.2.0 on 2026-08-20 and, after a long gap, v1.1.2 in June 2022. The repository is not archived, the default branch is dev, and the last push was on 2026-08-31.

The mechanism is a DOM element rather than a change to the browser's own pointer. The library creates a cursor element, hides the native one, and moves the element to follow the pointer. That matters more than it sounds, because once the cursor is an element in the page, ordinary CSS applies to it: it can have a background, a border radius, a transition, a transform, a mix-blend-mode, and it can contain text, an icon or an image.

The states are what make it more than a dot. The options include class names for the cursor root, an inner element, a text element, a media element and its inner box, and separate class names for the hidden, text, icon, active and media states. The active state applies on mousedown, the text state when the pointer is over selectable text, the media state over an image or video, and the hidden state when the pointer enters an iframe.

There is a second way to set a state, through a data attribute whose name is configurable, and the README notes that it uses event delegation, so a state can be written directly in the HTML markup rather than attached by a script.

The result is that a designer can decide what the cursor looks like over a link, over a paragraph, over a video or over a button, and change it with CSS, without touching the library's code.

GSAP is not optional, and it must be registered first

The dependency section of the README lists one dependency, GSAP v3, and the quick start makes the order of operations explicit.

bash
npm install gsap --save
npm install mouse-follower --save

Then you import both and register GSAP with the library before you construct the cursor.

js
import MouseFollower from "mouse-follower";
import {gsap} from "gsap";

MouseFollower.registerGSAP(gsap);

const cursor = new MouseFollower();

The registerGSAP call is the part that trips people up. The library does not import GSAP itself, it uses the instance you hand it, and the movement, easing and skewing are all GSAP's work. Without registering GSAP first, the library has no engine to drive the movement, so the setup fails in a way that is easy to misread as a CSS problem rather than a missing dependency.

The reason for that design is size. Bundling GSAP into the package would add to the size of a library whose own badges advertise a small bundle size, and sharing one GSAP instance with the rest of a site's animations avoids duplication.

There is one more step in a bundler setup, and it is easy to miss. The README asks you to import the cursor styles from /src/scss/index.scss into your main SCSS file, because the library ships styles as Sass source rather than as a ready made stylesheet for the bundler path. The minified CSS exists, but it is produced by the build as part of the distribution.

The CDN route, with GSAP loaded before the library

If you do not want the Mouse Follower files in your project, the README says you can use it from a CDN, and gives both files you need.

html
<link rel="stylesheet" href="https://unpkg.com/mouse-follower@1/dist/mouse-follower.min.css">

<script src="https://unpkg.com/mouse-follower@1/dist/mouse-follower.min.js"></script>

The stylesheet and the script, in that order, because the script expects the classes the stylesheet defines.

The CDN version still needs GSAP, and the README is explicit that you have to import it before Mouse Follower if you do not already have it.

html
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.10.4/gsap.min.js"></script>
<script src="https://unpkg.com/mouse-follower@1/dist/mouse-follower.min.js"></script>
<script>
    var cursor = new MouseFollower();
</script>

The order in that snippet is the whole instruction: GSAP first, then the library, then the construction. The CSS link tag is not shown in that second block because the README presents the two files separately, and in a real page you would keep the link tag from the first block alongside it.

The build scripts explain where those CDN files come from. The build runs microbundle to produce the ES module, CommonJS and UMD bundles with type generation, then a second microbundle invocation writes dist/mouse-follower.min.js, and finally Sass compiles src/scss/index.scss into dist/mouse-follower.min.css with the compressed style. The package publishes the dist/index.js as main, the ES module build as module, the UMD build for unpkg, and the type declarations.

States are classes, a data attribute and a delegation map

The state system is configurable, and the options list is long enough that it is worth reading as a group rather than line by line.

The first block shows the defaults.

js
const cursor = new MouseFollower({
    el: null,
    container: document.body,
    eventsTarget: document.documentElement,
    className: 'mf-cursor',
    innerClassName: 'mf-cursor-inner',
    textClassName: 'mf-cursor-text',
    mediaClassName: 'mf-cursor-media',
    mediaBoxClassName: 'mf-cursor-media-box',
    iconSvgClassName: 'mf-svgsprite',
    iconSvgNamePrefix: '-',

Three of those defaults matter for behaviour. el is null, which means the cursor element is created automatically, but you can pass an existing element to use instead. container is document.body, the element the cursor is appended to, and eventsTarget is document.documentElement, the element the pointer events are listened on. Separating the two lets you put the visual cursor in one place and listen on another.

The stateDetection option is the mapping that decides which states appear over which elements.

js
    stateDetection: {
        '-pointer': 'a,button',
        '-hidden': 'iframe'
    },

By default, links and buttons get the pointer state and iframes get the hidden state, and the README notes that this mapping uses event delegation, so it applies to elements that appear later as well.

The other way to set a state is the data attribute, whose name is the dataAttr option, defaulting to cursor. The README describes it as the name of the data attribute for changing cursor state directly in HTML markup, again through event delegation.

Speed, easing, skew and the timeouts that have to match your CSS

The rest of the option list is about how the cursor moves and how it looks while it moves.

speed defaults to 0.55 and is the cursor movement speed, and ease defaults to expo.out, the timing function of the movement, with a link to the GSAP easing documentation. overwrite defaults to true and controls whether the cursor position is overwritten or preserved when the mousemove event fires, with a link to the GSAP overwrite modes. A speed of zero would freeze the cursor, and a speed near one makes it track the pointer closely, so the default sits somewhere between the two.

The skewing family is the visual signature of the library. skewing is the default factor, with separate values for the text, icon and media states, so a cursor can lean differently depending on what it is over. skewingDelta is the base delta and skewingDeltaMax caps it, and setting any of these to zero disables the effect in that mode. stickDelta is the stick effect delta, which is what makes the cursor appear to lag and catch up with the pointer when it moves over an element.

Three timeouts tie the JavaScript to the CSS, and this is where a mismatch shows up. showTimeout, 20 by default, is the delay before showing and the README says it may be useful for the spawn animation to work properly. hideTimeout and hideMediaTimeout are both 300 by default, and the README says the hiding delay should be equal to the CSS hide animation time.

That last instruction is the important one. The library does not know how long your CSS animation runs, so the number is yours to keep in step, and a mismatch shows as a cursor that vanishes before or after the animation expects.

Showing and hiding the cursor from your own code

The advanced usage section starts with two methods, show and hide, and the example attaches them to mouseenter and mouseleave listeners on an element.

js
const cursor = new MouseFollower();
const el = document.querySelector('.my-element');

el.addEventListener('mouseenter', () => {
    cursor.hide();
});

The visible option controls whether the cursor is visible by default, and visibleOnState, off by default, automatically shows and hides the cursor when a state is added, which the README says can be useful when implementing a hidden cursor follower.

The related options are hideOnLeave, true by default, which hides the cursor when the mouse leaves the event target, and initialPos, an array of x and y coordinates for the initial cursor position, which matters for pages that reveal the cursor on a particular element rather than at the pointer.

The icon options are worth a note too. iconSvgSrc and iconSvgNamePrefix point at an SVG sprite, and the README says to leave the sprite source blank if you are not using SVG sprites, which tells you the library looks for a sprite element by class name and prefix rather than bundling one.

The repository is small: src/ for the source, a .browserslistrc for browser targets, an .eslintrc.json, a tsconfig.json for the generated types, and a package.json whose only devDependencies are eslint, sass and microbundle.

Where a custom cursor is the wrong decision

Three situations argue against it.

The first is any interface where the pointer carries meaning. A data table, a form, a diagram editor or a mapping tool depends on precise hover feedback, and a cursor that eases toward the pointer rather than tracking it introduces lag exactly where the user needs accuracy. The library's own defaults acknowledge this, with a speed that is deliberately slower than the pointer and a skew effect that tilts the cursor, both of which are design choices for a showcase page rather than for a tool.

The second is touch and hybrid devices. The library tracks a mouse pointer, and on a touch screen there is no pointer to follow. The repository carries a .browserslistrc, but the options are about pointer behaviour, and none of the visible options describes a touch fallback, so a mobile visit is left with whatever the native cursor does.

The third is accessibility and user preference. Some people replace the pointer deliberately because a coloured shape is easier to see. Hiding the native cursor removes the system's own affordances, and a site that is entirely pointer driven with a custom cursor is worse for anyone who has difficulty tracking a fast or small target.

Within its own lane, the library does one thing well and does it in a small number of kilobytes, with a state system that is genuinely configurable rather than hard coded, and a dependency you can share with the rest of your animation work.

Editorial conclusion

mouse-follower fits a marketing or portfolio site that wants a cursor that reacts to what it is over, because the states, the data attribute and the class names are all exposed as options rather than hard coded, and the movement, easing and skew are handled by GSAP. It is the wrong choice for an application where the pointer is the primary input, because a custom cursor that lags, hides or misreports hover state makes a form worse, and the accessibility cost is real on touch and on systems where users replace the pointer deliberately. Before adopting it, install GSAP first and call MouseFollower.registerGSAP(gsap), import the SCSS from src/scss/index.scss, and check the stateDetection map against the elements your page actually uses.

Frequently asked questions

How do I install mouse-follower and get it working?

Run npm install gsap --save and npm install mouse-follower --save, then import both, call MouseFollower.registerGSAP(gsap) and construct new MouseFollower(). If you use a bundler you also need to import the cursor styles from /src/scss/index.scss into your main SCSS file.

Do I have to install GSAP separately?

Yes. The README lists GSAP v3 as the dependency and the library does not bundle it, so you install it and pass it in with MouseFollower.registerGSAP(gsap) before creating the cursor. The CDN example loads gsap.min.js before the mouse-follower script for the same reason.

How do I change the cursor state over different elements?

Through the stateDetection option, which maps states to selectors such as '-pointer': 'a,button' and '-hidden': 'iframe' and uses event delegation, or through the data attribute whose name is set by the dataAttr option and defaults to cursor, so a state can be written directly in the HTML markup.

What is the hideTimeout option for?

It is the hiding delay, 300 by default, and the README says it should be equal to the CSS hide animation time so that the JavaScript and the stylesheet agree on when the cursor disappears. hideMediaTimeout works the same way for the media state.

When was the last mouse-follower release?

Version 1.2.1 was published on 2026-08-31, after v1.2.0 on 2026-08-20. The gap before that is long, since v1.1.2 dates from June 2022, and the repository's default branch is dev with the last push on 2026-08-31.

Official sources

  1. Cuberto/mouse-follower on GitHub
  2. Issues
  3. License: MIT
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/cuberto-mouse-follower.svg)](https://hysenlabs.com/projects/cuberto-mouse-follower)

Was this useful?