VibeUI
ComponentsDocs
VibeUI

Components with taste, that you own. Copy the source, retint the tokens, keep every line.

Library

  • All components
  • Signature pieces
  • Text effects
  • Footers

Docs

  • Introduction
  • Installation
  • Theming
  • CLI

Project

  • GitHub
  • Agents and MCP
  • Issues
  • MIT license
© 2026 VibeUI · MIT licensed
  1. Components
  2. Hover effects
  3. Magnetic Button

Magnetic Button

Button that leans toward the cursor and springs back on leave.

Hover effectsClient component

Install

bash
npx @agents-npm-packages/vibeui add magnetic-button --dir ./src/components/ui

Import

tsx
import { MagneticButton } from "@/components/ui/magnetic-button"

API

Generated from the component's TypeScript types, so it always matches the source. * marks a required prop.

<MagneticButton>

also accepts every ButtonHTMLAttributes<HTMLButtonElement> attribute

PropTypeDefaultDescription
strengthnumber0.35Fraction of the cursor offset the button follows (0–1).

Source

src/components/ui/magnetic-button.tsx
"use client";

import * as React from "react";
import { cn } from "@/lib/utils";

/** Button that leans toward the cursor and springs back on leave. */
export function MagneticButton({
  children,
  className,
  strength = 0.35,
  ...props
}: React.ButtonHTMLAttributes<HTMLButtonElement> & {
  /** Fraction of the cursor offset the button follows (0–1). */
  strength?: number;
}) {
  const ref = React.useRef<HTMLButtonElement>(null);
  const inner = React.useRef<HTMLSpanElement>(null);

  const move = (e: React.MouseEvent) => {
    const el = ref.current;
    if (!el) return;
    const r = el.getBoundingClientRect();
    const x = e.clientX - (r.left + r.width / 2);
    const y = e.clientY - (r.top + r.height / 2);
    el.style.transition = "transform 0.12s linear";
    el.style.transform = `translate(${x * strength}px, ${y * strength}px)`;
    if (inner.current) {
      inner.current.style.transform = `translate(${x * strength * 0.4}px, ${y * strength * 0.4}px)`;
    }
  };

  const reset = () => {
    const el = ref.current;
    if (!el) return;
    el.style.transition = "transform 0.6s var(--ease-spring)";
    el.style.transform = "translate(0,0)";
    if (inner.current) inner.current.style.transform = "translate(0,0)";
  };

  return (
    <button
      ref={ref}
      type="button"
      onMouseMove={move}
      onMouseLeave={reset}
      className={cn(
        "relative inline-flex h-12 items-center justify-center rounded-md bg-ink px-6 text-[15px] font-medium text-surface shadow-quiet shadow-inset transition-shadow duration-300 hover:shadow-lift",
        className,
      )}
      {...props}
    >
      <span
        ref={inner}
        className="inline-flex items-center gap-2 transition-transform duration-300 ease-out"
      >
        {children}
      </span>
    </button>
  );
}

Exports MagneticButton. Peer dependencies: clsx, tailwind-merge.

PreviousHover TiltNextMagnetic Link