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

@tonybonet/magnetic

v0.4.0

Published

Pointer-tracking magnetic UI elements for React with spring physics and a shared orchestrator.

Downloads

644

Readme

@tonybonet/magnetic

Pointer-tracking magnetic UI elements with a shared orchestrator. One listener. One rAF loop. Zero compromises.

Playground → · GitHub

Pepper your page with magnetic buttons, CTAs, and tiles — each one subtly pulls toward the cursor. The shared orchestrator means one pointermove listener, one requestAnimationFrame loop, and IntersectionObserver gating regardless of how many elements you mount. No per-element listeners. No frame-budget tax.

Install

npm install @tonybonet/magnetic
# or
pnpm add @tonybonet/magnetic
# or
yarn add @tonybonet/magnetic

Peer dependencies: react (≥19.0.0) and motion (≥12.0.0).

Quickstart

Component (JSX)

import { Magnetic } from "@tonybonet/magnetic/component";

export function CTA() {
  return (
    <Magnetic options={{ maxOffset: 20 }}>
      <button>Hover me</button>
    </Magnetic>
  );
}

<Magnetic> owns its own <LazyMotion> boundary. If your app already wraps with LazyMotion at the root, Motion deduplicates — the wider feature set wins.

Headless hook

import { m } from "motion/react";
import { useMagnetic } from "@tonybonet/magnetic";

function Tile() {
  const magnetic = useMagnetic({ maxOffset: 20 });
  return (
    <m.div ref={magnetic.ref} style={magnetic.style}>
      Tile content
    </m.div>
  );
}

Presets

import { Magnetic } from "@tonybonet/magnetic/component";
import { TACTILE, FLUID, PLAYFUL, SUBTLE, ATTRACTION } from "@tonybonet/magnetic/presets";

// Use a preset directly
<Magnetic options={TACTILE}>
  <button>Snappy button</button>
</Magnetic>;

// Compose with .with()
<Magnetic options={FLUID.with({ maxOffset: 60, trigger: "hover" })}>
  <div>Customized card</div>
</Magnetic>;

| Preset | Feel | maxDist | maxOff | Best for | |---|---|---|---|---| | TACTILE | ⚡ Snappy, responsive | 200 | 16 | Buttons & controls | | FLUID | 🌊 Luxurious, heavy | 400 | 48 | Cards & premium UI | | PLAYFUL | 🎈 Bouncy, energetic | 280 | 32 | Badges & playful UI | | SUBTLE | 👁️ Barely-there | 120 | 6 | Text links & inline | | ATTRACTION | 🧲 Aggressive reach | 500 | 80 | Hero CTAs & focal |

Each preset is a full MagnetOptions object with a .with() method for composition:

// Start from a preset, override anything
TACTILE.with({ maxOffset: 24 })
FLUID.with({ maxDistance: 500, snap: false })
ATTRACTION.with({ trigger: "hover" })

// Or compose from defaults
MAGNET_DEFAULTS.with({ maxOffset: 20, smooth: false })

Exports

| Subpath | What | |---|---| | @tonybonet/magnetic | useMagnetic hook + UseMagneticResult type | | @tonybonet/magnetic/component | Magnetic JSX component + MagneticProps type | | @tonybonet/magnetic/presets | TACTILE, FLUID, PLAYFUL, SUBTLE, ATTRACTION, MAGNET_DEFAULTS + MagnetPreset type |

Why a Shared Orchestrator?

The naive approach: every magnetic element attaches its own pointermove listener. 50 buttons = 50 listeners + 50 reflows per frame. Budget blown.

Magnetic uses a singleton orchestrator that shares:

  • One window.pointermove listener (passive)
  • One requestAnimationFrame loop (frame-coalesced)
  • One IntersectionObserver (gates off-screen elements out of the per-frame cost)
  • One ResizeObserver (invalidates cached bounding rects on layout change)
┌─────────────────────────────────────────────┐
│              MagnetOrchestrator              │
│  ┌───────────┐  ┌──────┐  ┌───────────────┐ │
│  │pointermove│  │ rAF  │  │IntersectionObs│ │
│  └─────┬─────┘  └──┬───┘  └───────┬───────┘ │
│        └───────────┼──────────────┘         │
│                    ▼                         │
│         ┌─────────────────────┐              │
│         │  Active Elements    │              │
│         │  (on-screen only)   │              │
│         └─────────────────────┘              │
└─────────────────────────────────────────────┘

Window blur and document.visibilitychange reset all active elements to avoid stuck offsets when the user tabs away.

API

useMagnetic(options?)

Primary hook. Returns { ref, x, y, style, enabled, isComposing, reset }.

| Field | Type | Description | |---|---|---| | ref | (node: T \| null) => void | Attach to the element that should track the pointer | | x | MotionValue<number> | Spring-animated X offset | | y | MotionValue<number> | Spring-animated Y offset | | style | { x, y } | Pass directly to a motion component's style prop | | enabled | boolean | Whether the magnetic effect is active | | isComposing | boolean | Whether the pointer is within maxDistance | | reset | () => void | Reset to origin (stable identity across renders) |

<Magnetic options? style? magneticKey? ...divProps>

JSX convenience wrapper. Renders a motion.div with magnetic behavior.

| Prop | Type | Description | |---|---|---| | options | MagnetOptions | Configuration (see below) | | style | MotionStyle | Additional motion styles to apply | | magneticKey | Key | Override the derived structural key for forced remount | | ...rest | HTMLMotionProps<"div"> | All standard motion.div props |

MagnetOptions

| Option | Type | Default | Category | Description | |---|---|---|---|---| | enabled | boolean | true | Structural | Turns the effect on/off (triggers remount) | | maxDistance | number | 320 | Structural | Radius from center where pull begins | | maxOffset | number | 64 | Structural | Maximum translation toward the pointer | | originX | number | 0.5 | Structural | Horizontal anchor (0 = left, 1 = right) | | originY | number | 0.5 | Structural | Vertical anchor (0 = top, 1 = bottom) | | elastic | boolean | true | Structural | Use sine-curve mapping for pull strength | | smooth | boolean | true | Visual | true = SMOOTH preset, false = TACTILE | | snap | boolean | true | Visual | true = bounce back, false = linear return | | smoothSpring | SpringOptions | — | Visual | Override the hover spring | | snapSpring | SpringOptions | — | Visual | Override the return spring | | trigger | "distance" \| "hover" \| "click" | "distance" | Structural | Activation mode |

Structural options trigger a remount when changed. Visual options update live.

Accessibility

useMagnetic calls useReducedMotion() from Motion and disables itself when the user has prefers-reduced-motion: reduce set. No configuration needed.

Compatibility

| Package | Range | |---|---| | react | ^19.0.0 | | motion | ^12.0.0 |

License

MIT