https://github.com/andreasnicolaou/halo-cursor
Framework-agnostic animated cursor for Angular, React, Vue, and plain TypeScript apps.
https://github.com/andreasnicolaou/halo-cursor
angular animated-cursor cursor cursor-effects custom-cursor esm framework-agnostic halo-cursor javascript react svelte typescript umd vue
Last synced: 1 day ago
JSON representation
Framework-agnostic animated cursor for Angular, React, Vue, and plain TypeScript apps.
- Host: GitHub
- URL: https://github.com/andreasnicolaou/halo-cursor
- Owner: andreasnicolaou
- License: mit
- Created: 2026-03-13T14:45:12.000Z (4 months ago)
- Default Branch: main
- Last Pushed: 2026-06-25T22:56:02.000Z (11 days ago)
- Last Synced: 2026-06-26T00:13:33.816Z (11 days ago)
- Topics: angular, animated-cursor, cursor, cursor-effects, custom-cursor, esm, framework-agnostic, halo-cursor, javascript, react, svelte, typescript, umd, vue
- Language: TypeScript
- Homepage: https://andreasnicolaou.github.io/halo-cursor/
- Size: 175 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
- Codeowners: .github/CODEOWNERS
Awesome Lists containing this project
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](https://andreasnicolaou.github.io/halo-cursor/))
It works great with:
- Angular
- React
- Vue
- Svelte
- plain HTML / TypeScript / JavaScript
---










## 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
```sh
# npm
npm install @andreasnicolaou/halo-cursor
# yarn
yarn add @andreasnicolaou/halo-cursor
# pnpm
pnpm add @andreasnicolaou/halo-cursor
```
### CDN / Direct Usage (UMD)
```html
```
**UMD (global `halo` variable):**
```html
// window.halo is the UMD global
const cursor = new halo.Cursor({
hideNativeCursor: true,
});
cursor.mount();
// later: cursor.destroy();
```
### ESM (recommended for bundlers)
```ts
import { Cursor } from '@andreasnicolaou/halo-cursor';
const cursor = new Cursor({
outerSize: 40,
innerSize: 8,
hideNativeCursor: true,
});
cursor.mount();
```
### CJS (Node / CommonJS)
```js
const { Cursor } = require('@andreasnicolaou/halo-cursor');
const cursor = new Cursor({
hideNativeCursor: true,
});
cursor.mount();
```
## Quick Usage Example
```ts
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` | `true` | 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
```ts
import { AfterViewInit, Component, ElementRef, OnDestroy, ViewChild } from '@angular/core';
import { Cursor } from '@andreasnicolaou/halo-cursor';
@Component({
selector: 'app-root',
template: `
`,
})
export class AppComponent implements AfterViewInit, OnDestroy {
@ViewChild('scope', { static: true }) scopeRef!: ElementRef;
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
```tsx
import { useEffect, useRef } from 'react';
import { Cursor } from '@andreasnicolaou/halo-cursor';
export function AppCursorScope() {
const ref = useRef(null);
useEffect(() => {
if (!ref.current) return;
const cursor = new Cursor({ rootElement: ref.current, hideNativeCursor: true });
cursor.mount();
return () => cursor.destroy();
}, []);
return
{/* your app */};
}
```
### Vue
```ts
import { onBeforeUnmount, onMounted, ref } from 'vue';
import { Cursor } from '@andreasnicolaou/halo-cursor';
const scopeRef = ref(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:
```vue
```
## 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.