Sora UI

Magnetic Cards

A fanned stack of cards that fling away from the cursor on release, with GSAP InertiaPlugin easing each one back to rest.

Made by Axyl

Move your cursor across a card, then release — it flings off based on how fast the cursor was moving, and eases back to rest.

Loading...

MagneticCards tracks cursor speed per card while it's hovered (mousemove/mouseenter), then on mouseleave hands that speed to GSAP's InertiaPlugin as a starting velocity, targeting the card's own rest position/rotation as the end value — the same per-card fling-and-settle model as Truus.co's cards.

Installation

File Structure

magnetic-cards.tsx
use-prefers-reduced-motion.tsx

Usage

import { MagneticCards } from '@/components/sora-ui/effects/magnetic-cards';

const items = [
  { src: '/cards/card-1.jpg', alt: 'Card one' },
  { src: '/cards/card-2.jpg', alt: 'Card two' },
  { src: '/cards/card-3.jpg', alt: 'Card three' },
  { src: '/cards/card-4.jpg', alt: 'Card four' },
];

export default function Page() {
  return (
    <div className="h-[420px] w-full">
      <MagneticCards items={items} />
    </div>
  );
}

Props

PropTypeDefault
items?
MagneticCardsItem[]
-
layout?
MagneticCardsLayout[]
-
velocityMultiplier?
number
20
rotationVelocityMultiplier?
number
1.5
cardClassName?
string
-
className?
string
-

MagneticCardsItem

PropTypeDefault
src?
string
-
alt?
string
-

When to Use

Use MagneticCards for hero sections, portfolio spotlights, or landing pages where a small set of images (3-6 works best) deserves a tactile, playful entrance. The fling reads best with generous surrounding space — give the root element real height (e.g. a fixed-height section) rather than letting it collapse to content size.

For image grids that need to stay readable and scannable (e.g. a gallery or catalog), use a static grid instead — the constant motion here is decorative, not information-dense.

Accessibility

Cursor tracking is scoped to each card individually via mousemove/mouseenter/mouseleave, so it never hijacks page-wide input. When prefers-reduced-motion: reduce is set, no listeners are attached at all — cards render directly at their rest position/rotation with no motion, and the setting is watched live via usePrefersReducedMotion, so toggling it while the page is open updates immediately.

Credits

Inspired by Truus.co. Reimplemented for GSAP InertiaPlugin and React.

Built by Axyl. A motion-first component registry for React.

Last updated: 7/9/2026