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

hover-repel

v0.1.4

Published

![Hover Repel demo](./assets/demo.gif)

Readme

Hover Repel

Hover Repel demo

A tiny hover interaction library that makes elements react to your cursor with a quick directional “repel” movement.

Hover Repel pushes an element away based on the direction and speed of your pointer, then smoothly returns it to its original position. It is great for buttons, cards, icons, badges, product items, playful UI details, and landing pages.


Features

  • Direction-aware hover movement
  • Speed-based movement strength
  • Optional rotation/tilt effect
  • Works with vanilla HTML/JS, Vue, Nuxt, React, Astro, and more
  • Simple data-repel API
  • Vue directive support with v-repel
  • Optional stable hitbox with data-repel-container
  • Automatically creates a wrapper when needed
  • Lightweight core
  • TypeScript support
  • Reduced-motion friendly

Installation

npm install hover-repel
pnpm add hover-repel
yarn add hover-repel

Vanilla HTML / JavaScript

Use the browser build from a CDN.

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/hover-repel/dist/style.css">

<div data-repel class="card">
  Hover me
</div>

<script src="https://cdn.jsdelivr.net/npm/hover-repel/dist/browser.global.js"></script>
<script>
  HoverRepel.repel();
</script>

Example page:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Hover Repel Demo</title>

  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/hover-repel/dist/style.css">

  <style>
    body {
      min-height: 100vh;
      display: grid;
      place-items: center;
      background: #111;
    }

    .card {
      width: 180px;
      height: 240px;
      border-radius: 24px;
      background: #31e981;
    }
  </style>
</head>
<body>
  <div data-repel class="card"></div>

  <script src="https://cdn.jsdelivr.net/npm/hover-repel/dist/browser.global.js"></script>
  <script>
    HoverRepel.repel();
  </script>
</body>
</html>

You can also use local files:

<link rel="stylesheet" href="./style.css">

<div data-repel class="card"></div>

<script src="./browser.global.js"></script>
<script>
  HoverRepel.repel();
</script>

Bundler Usage

For Vite, Webpack, Parcel, Astro, React, Vue, Nuxt, and other bundlers:

import { repel } from "hover-repel";
import "hover-repel/style.css";

repel();

Basic markup:

<div data-repel class="card">
  Hover me
</div>

How It Works

Hover Repel uses a moving element and an optional stable hitbox.

<div data-repel-container>
  <div data-repel></div>
</div>

data-repel-container is the stable hover area.
data-repel is the element that visually moves.

For most cases, you only need this:

<div data-repel></div>

Hover Repel will automatically create a wrapper when needed.

Use a manual container when you want full control over the hitbox, spacing, or layout.


Vue

Register the Vue plugin once:

// main.ts
import { createApp } from "vue";
import App from "./App.vue";

import HoverRepelVue from "hover-repel/vue";
import "hover-repel/style.css";

const app = createApp(App);

app.use(HoverRepelVue);

app.mount("#app");

Then use v-repel anywhere:

<template>
  <button v-repel class="button">
    Hover me
  </button>
</template>

With options:

<template>
  <button
    v-repel="{
      maxStrength: 100,
      maxRotate: 35,
      resetDelay: 250
    }"
  >
    Hover me harder
  </button>
</template>

Global defaults:

app.use(HoverRepelVue, {
  maxStrength: 90,
  maxRotate: 30,
  resetDelay: 250
});

Local directive usage:

<script setup lang="ts">
import { vRepel } from "hover-repel/vue";
import "hover-repel/style.css";
</script>

<template>
  <button v-repel>
    Hover me
  </button>
</template>

Nuxt

Add the CSS globally:

// nuxt.config.ts
export default defineNuxtConfig({
  css: ["hover-repel/style.css"]
});

Create a client plugin:

// plugins/hover-repel.client.ts
import HoverRepelVue from "hover-repel/vue";

export default defineNuxtPlugin((nuxtApp) => {
  nuxtApp.vueApp.use(HoverRepelVue);
});

Use it in any component:

<template>
  <NuxtLink v-repel to="/contact" class="cta">
    Contact us
  </NuxtLink>
</template>

With global defaults:

// plugins/hover-repel.client.ts
import HoverRepelVue from "hover-repel/vue";

export default defineNuxtPlugin((nuxtApp) => {
  nuxtApp.vueApp.use(HoverRepelVue, {
    maxStrength: 90,
    maxRotate: 30,
    resetDelay: 250
  });
});

React

React can use the vanilla API.

import { useEffect, useRef } from "react";
import { attachRepel } from "hover-repel";
import "hover-repel/style.css";

export function Card() {
  const ref = useRef(null);

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

    const destroy = attachRepel(ref.current);

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

  return (
    <div ref={ref} data-repel className="card">
      Hover me
    </div>
  );
}

Data Attributes

| Attribute | Description | Default | | --- | --- | --- | | data-repel | Element that should move | Required for vanilla usage | | data-repel-container | Stable hover area | Optional | | data-repel-strength | Maximum movement distance in pixels | 75 | | data-repel-rotate | Maximum rotation in degrees | 26 | | data-repel-reset | Delay before returning to rest in milliseconds | 300 |

Example:

<div
  data-repel
  data-repel-strength="90"
  data-repel-rotate="30"
  data-repel-reset="250"
>
  Hover me
</div>

In Vue/Nuxt, v-repel automatically adds data-repel.


JavaScript API

repel(options?)

Initializes all matching repel elements.

import { repel } from "hover-repel";

const destroy = repel();

With options:

const destroy = repel({
  root: document.querySelector(".section"),
  maxStrength: 90,
  maxRotate: 30,
  resetDelay: 250
});

Cleanup:

destroy();

attachRepel(element, options?)

Attaches the effect to one element.

import { attachRepel } from "hover-repel";

const element = document.querySelector(".card");
const destroy = attachRepel(element);

This is useful for components and dynamic elements.


Options

type RepelOptions = {
  minStrength?: number;
  maxStrength?: number;
  speedMultiplier?: number;

  minRotate?: number;
  maxRotate?: number;
  rotateMultiplier?: number;

  resetDelay?: number;

  containerSelector?: string;
  autoWrap?: boolean;
};

For repel() you can also pass:

type RepelInitOptions = RepelOptions & {
  root?: ParentNode;
  targetSelector?: string;
};

| Option | Description | Default | | --- | --- | --- | | root | Root used by repel() to find targets | document | | targetSelector | Selector used by repel() | [data-repel] | | containerSelector | Selector for the stable hover container | [data-repel-container] | | autoWrap | Automatically creates a wrapper when no container exists | true | | minStrength | Minimum movement distance | 18 | | maxStrength | Maximum movement distance | 75 | | speedMultiplier | How much pointer speed affects movement | 130 | | minRotate | Minimum rotation amount | 8 | | maxRotate | Maximum rotation amount | 26 | | rotateMultiplier | How much pointer speed affects rotation | 55 | | resetDelay | Delay before returning to rest | 300 |


CSS Variables

Hover Repel uses CSS variables for the transform:

[data-repel] {
  --repel-x: 0px;
  --repel-y: 0px;
  --repel-r: 0deg;
}

Customize animation timing:

[data-repel] {
  --repel-duration: 900ms;
  --repel-ease: cubic-bezier(.16, 1.8, .35, 1);
}

Styling Example

.card {
  width: 220px;
  height: 280px;
  border-radius: 2rem;
  background: linear-gradient(135deg, #31e981, #16c7ff);
  box-shadow: 0 24px 80px rgba(0, 0, 0, 0.25);
}
<div data-repel class="card"></div>

Package Exports

import { repel, attachRepel } from "hover-repel";
import HoverRepelVue, { vRepel } from "hover-repel/vue";
import "hover-repel/style.css";

For vanilla HTML:

<script src="https://cdn.jsdelivr.net/npm/hover-repel/dist/browser.global.js"></script>
<script>
  HoverRepel.repel();
</script>

Common Issues

The effect does not work

Make sure the CSS is loaded:

import "hover-repel/style.css";

or in plain HTML:

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/hover-repel/dist/style.css">

Nuxt only works on first load

Use the Nuxt client plugin with v-repel instead of manually calling repel().

// plugins/hover-repel.client.ts
import HoverRepelVue from "hover-repel/vue";

export default defineNuxtPlugin((nuxtApp) => {
  nuxtApp.vueApp.use(HoverRepelVue);
});

The layout changes unexpectedly

Hover Repel automatically creates a wrapper when no data-repel-container exists.

For full layout control:

<div data-repel-container>
  <div data-repel></div>
</div>

Or disable auto wrapping:

repel({
  autoWrap: false
});

Browser Support

Hover Repel uses modern pointer events and CSS transforms. It works in all modern browsers.


License

MIT