Magnetic button
Free · MITButton and its label are pulled toward a nearby cursor with a smooth falloff and spring back on leave; off for touch and reduced motion.
Installation
$ npx shadcn@latest add https://goodui.dev/r/magnetic-button.jsonThe file lands in components/goodui/magnetic-button.tsx and is yours to edit. With the @goodui registry added, npx shadcn@latest add @goodui/magnetic-button works too.
Usage
import { ArrowRight } from "lucide-react"
import { MagneticButton } from "@/components/goodui/magnetic-button"
export function FooterCta({ onStart }: { onStart: () => void }) {
return (
<MagneticButton size="lg" onClick={onStart}>
Get started free
<ArrowRight className="rtl:rotate-180" />
</MagneticButton>
)
}When to use
- A hero CTA or footer "Get started" on a marketing site where a playful, tactile hover fits the brand.
- Round icon buttons (social links, a floating chat button) that should feel physical under the mouse.
- Portfolio and agency pages where pointer interactions are part of the design.
- Dense app UI (tables, toolbars, forms): a moving target slows precise clicking. Use button.
- Touch-first screens: the effect is off on touch and coarse pointers, so it adds nothing there. Use button or ripple-button.
- A hover lift without pointer tracking: button with animation="lift" is simpler.
| If you need | Use |
|---|---|
| Subtle pull for an app-like CTA | strength={0.2} labelStrength={0.1} |
| Strong, playful pull | strength={0.5} labelStrength={0.3} radius={120} |
| Round icon button | size="icon" aria-label="…" |
| Only react when the cursor is on the button | radius={0} |
Examples
Round icon button with a stronger pull
<MagneticButton size="icon" variant="outline" strength={0.5} radius={100} aria-label="Chat with support">
<MessageCircle />
</MagneticButton>API reference
| Prop | Type | Default |
|---|---|---|
| variant "default" | "secondary" | "outline" | "ghost", matching shadcn's button variants. | "outline" | "default" | "ghost" | "secondary" | "default" |
| size "sm" | "md" | "lg" | "icon"; "icon" is a round 44px button. | "sm" | "md" | "lg" | "icon" | "md" |
| strength Share of the pointer offset the button follows (0–1). | number | 0.35 |
| labelStrength Extra share for the label, for a parallax feel. | number | 0.2 |
| radius Distance in px outside the button where the pull starts. | number | 70 |
Also accepts every <button> attribute.
Accessibility
| Enter / Space | Activates the button (native <button>). |
| Tab | Moves focus; a focus ring shows on keyboard focus only. Keyboard focus does not move the button. |
- Renders a native <button> (motion.button) inside an inline-flex <span> wrapper that is used to measure the resting position.
- The effect only runs for a fine pointer that can hover (mouse, trackpad); touch input and keyboard users get a normal static button.
- Disabled buttons do not move.
Motion
- The button moves toward the pointer by `strength` (0.35) of the offset and the label by `labelStrength` (0.2), both on a spring (stiffness 220, damping 15, mass 0.6).
- The pull is full over the button and eases to zero at `radius` px (70) outside it, using a smoothstep curve; leaving springs it back to the center.
- Tap scales to 0.95.
- Listening stops while the button is off-screen, while the tab is hidden and when the pointer is not fine.
- Reduced motion: no pull and no tap scale.
- Uses motion values and springs, so pointer moves do not re-render React. The window pointermove listener is only attached while the button is visible.
Notes for AI
Coding agents read these rules through MCP and llms.txt, or paste this page with Copy page.
- variant values are default, secondary, outline, ghost; sizes sm, md, lg, icon.
- strength and labelStrength are fractions of the pointer offset (0 to 1), not pixels; radius is in px.
- className goes on the button; the wrapping <span> is internal, so use a parent element for layout spacing.
- Pass icons and text as children; they move together as the label.
FAQ
Does it work on phones?
It renders as a normal button. The pull only runs for a fine pointer that can hover (mouse or trackpad).
Can I use it on several buttons?
Yes. Each button only listens while it is visible, but keep it to a few buttons so the page does not feel jumpy.
Related
One button with ten variants, six tones, seven shadows, ten animations, three shapes and five sizes that all combine, plus loading and icon slots.
Material-style ink ripple that grows from the exact press point and fades on release, pure CSS, across five variants and icon buttons.
Tilts toward the pointer on a spring with a moving glare, a shifting shadow and layers that float at their own depth.
A light sweep runs across the button. Pure CSS; the sweep is removed for reduced motion.