npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@andreasnicolaou/halo-cursor

v1.0.0

Published

Framework-agnostic animated cursor for Angular, React, Vue, and plain TypeScript apps.

Downloads

82

Readme

Halo Cursor – Framework‑Agnostic Animated Cursor

Halo Cursor is a lightweight, framework‑agnostic animated cursor halo for modern web apps. It follows the pointer, highlights interactive elements, and can completely hide the native cursor – all in plain TypeScript with zero runtime dependencies. (demo)

It works great with:

  • Angular
  • React
  • Vue
  • Svelte
  • plain HTML / TypeScript / JavaScript

TypeScript GitHub contributors GitHub License GitHub Actions Workflow Status GitHub package.json version

ESLint Prettier Jest Maintenance NPM Downloads

Key Features

  • Framework‑agnostic – Works in any app that can run JavaScript in the browser
  • Animated halo – Smooth lerped motion with inner dot + outer halo
  • Interactive‑aware – Highlights buttons/links and other interactive elements
  • Motion‑safe – Respects prefers-reduced-motion and coarse pointers (e.g. touch)
  • Cursor control – Optional full native cursor hiding with hideNativeCursor
  • Scoped & controllable – Scope to a container with rootElement, and use pause / resume
  • Tiny & dependency‑free – Just one class and sensible defaults

Installation

# npm
npm install @andreasnicolaou/halo-cursor

# yarn
yarn add @andreasnicolaou/halo-cursor

# pnpm
pnpm add @andreasnicolaou/halo-cursor

CDN / Direct Usage (UMD)

<!-- unpkg CDN (latest version, unminified) -->
<script src="https://unpkg.com/@andreasnicolaou/halo-cursor/dist/index.umd.js"></script>

<!-- unpkg CDN (latest version, minified) -->
<script src="https://unpkg.com/@andreasnicolaou/halo-cursor/dist/index.umd.min.js"></script>

<!-- jsDelivr CDN (unminified) -->
<script src="https://cdn.jsdelivr.net/npm/@andreasnicolaou/halo-cursor/dist/index.umd.js"></script>

<!-- jsDelivr CDN (minified) -->
<script src="https://cdn.jsdelivr.net/npm/@andreasnicolaou/halo-cursor/dist/index.umd.min.js"></script>

UMD (global halo variable):

<script src="https://unpkg.com/@andreasnicolaou/halo-cursor/dist/index.umd.min.js"></script>
<script>
  // window.halo is the UMD global
  const cursor = new halo.Cursor({
    hideNativeCursor: true,
  });

  cursor.mount();

  // later: cursor.destroy();
</script>

ESM (recommended for bundlers)

import { Cursor } from '@andreasnicolaou/halo-cursor';

const cursor = new Cursor({
  outerSize: 40,
  innerSize: 8,
  hideNativeCursor: true,
});

cursor.mount();

CJS (Node / CommonJS)

const { Cursor } = require('@andreasnicolaou/halo-cursor');

const cursor = new Cursor({
  hideNativeCursor: true,
});

cursor.mount();

Quick Usage Example

import { Cursor } from '@andreasnicolaou/halo-cursor';

const cursor = new Cursor({
  color: '#6366f1',
  hoverColor: '#818cf8',
  outerSize: 36,
  hoverOuterSize: 52,
  hideNativeCursor: true,
  lerp: 0.12,
});

// Attach to the whole document
cursor.mount();

// Temporarily pause animation/interaction (e.g. while a modal is open)
cursor.pause();
cursor.resume();

// Clean up when leaving the page / unmounting your app
// cursor.destroy();

API

Cursor methods

| Method | Description | | ------------------------ | ------------------------------------------------------------------------------------------ | | new Cursor(options?) | Creates a new halo cursor instance with the given options. | | mount() | Injects styles, creates DOM elements, attaches events, and starts the animation loop. | | destroy() | Stops the loop, removes event listeners, DOM nodes, and restores the native cursor. | | updateOptions(options) | Merges new options into the existing ones and reinjects the stylesheet while mounted. | | pause() | Temporarily pauses tracking and hides the halo off‑screen without destroying the instance. | | resume() | Resumes tracking and animation after a previous pause(). |

CursorOptions

| Property | Type | Default | Description | | ------------------------ | --------------------- | ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------- | | outerSize | number | 36 | Size of the outer halo ring in pixels | | innerSize | number | 6 | Size of the inner dot in pixels | | hoverOuterSize | number | 52 | Outer ring size on hover | | clickOuterSize | number | 26 | Outer ring size on click | | color | string | '#6366f1' | Base color of the cursor halo | | hoverColor | string | '#818cf8' | Color on hover state | | outerBorderColor | string | 'rgba(99, 102, 241, 0.7)' | Outer ring border color | | hoverBorderColor | string | 'rgba(129, 140, 248, 1)' | Outer ring border color on hover | | outerBackground | string | 'transparent' | Outer ring background fill | | hoverBackground | string | 'rgba(99, 102, 241, 0.08)' | Background fill on hover | | clickBackground | string | 'rgba(99, 102, 241, 0.18)' | Background fill on click | | zIndex | number | 9999 | CSS z-index for cursor elements | | lerp | number | 0.12 | Smoothing factor for cursor movement (0–1, clamped). Higher = snappier, lower = smoother/more lag | | hideNativeCursor | boolean | false | Hide the native browser cursor | | interactiveSelectors | string | 'a, button, [role="button"], input, textarea, select, label, [tabindex="0"]' | CSS selectors for interactive elements | | classPrefix | string | 'halo-cursor' | CSS class name prefix for generated elements | | disableOnReducedMotion | boolean | true | Disable animations if reduced motion is preferred | | rootElement | HTMLElement \| null | null | Root element to mount cursor into |

Framework Examples

Angular

import { AfterViewInit, Component, ElementRef, OnDestroy, ViewChild } from '@angular/core';
import { Cursor } from '@andreasnicolaou/halo-cursor';

@Component({
  selector: 'app-root',
  template: ` <div #scope class="app-shell"><ng-content></ng-content></div> `,
})
export class AppComponent implements AfterViewInit, OnDestroy {
  @ViewChild('scope', { static: true }) scopeRef!: ElementRef<HTMLDivElement>;

  private cursor: Cursor | null = null;

  ngAfterViewInit(): void {
    this.cursor = new Cursor({ rootElement: this.scopeRef.nativeElement, hideNativeCursor: true });
    this.cursor.mount();
  }

  ngOnDestroy(): void {
    this.cursor?.destroy();
  }
}

React

import { useEffect, useRef } from 'react';
import { Cursor } from '@andreasnicolaou/halo-cursor';

export function AppCursorScope() {
  const ref = useRef<HTMLDivElement | null>(null);

  useEffect(() => {
    if (!ref.current) return;

    const cursor = new Cursor({ rootElement: ref.current, hideNativeCursor: true });
    cursor.mount();

    return () => cursor.destroy();
  }, []);

  return <div ref={ref}>{/* your app */}</div>;
}

Vue

import { onBeforeUnmount, onMounted, ref } from 'vue';
import { Cursor } from '@andreasnicolaou/halo-cursor';

const scopeRef = ref<HTMLElement | null>(null);
let cursor: Cursor | null = null;

onMounted(() => {
  if (!scopeRef.value) return;
  cursor = new Cursor({ rootElement: scopeRef.value, hideNativeCursor: true });
  cursor.mount();
});

onBeforeUnmount(() => {
  cursor?.destroy();
});

Use in your template:

<template>
  <div ref="scopeRef">
    <!-- your content -->
  </div>
</template>

Notes & Accessibility

  • The cursor auto‑disables itself on coarse pointers (e.g. touch devices).
  • When disableOnReducedMotion is true (default), it also disables itself when prefers-reduced-motion: reduce is enabled.
  • All DOM access happens inside mount() / destroy(), keeping the class safe to instantiate in SSR environments.

Contributing

Contributions, ideas, and bug reports are welcome. Feel free to open an issue or PR on the GitHub repository.