shadimage
xcn
componentanimated-cardssource: ok · hit

Animated Cards

Overlapping animated cards with subtle hover zoom and per-card rotation.

npx shadcn add "https://ui.radiumcoders.com/r/xcn/animated-cards.json"
free

No selection tags yet — empty means no evidence, not lack of support.

Dependencies

Bundle weight · medium
animationmotion

Files1 · source cached 10/14/2026, 1:20:29 PM

components/xcn/animated-cards.tsx
import { motion } from "motion/react"
import type { ReactElement, ReactNode } from "react"
import { cn } from "@/lib/utils"

export type CardMotionConfig = {
  x: number
  y: number
  zIndex: number
}

export type CardItem = {
  title: string
  description: string
  className?: string
  skeleton: ReactElement
  config: CardMotionConfig
  rotation: number
}

type AnimatedCardsProps = {
  cards: Array<CardItem>
  rounded?: boolean
  className?: string
  mobileContainerClassName?: string
  desktopContainerClassName?: string
  cardClassName?: string
  initialBlur?: number
  initialScale?: number
  desktopHoverScale?: number
  renderCardFooter?: (card: CardItem) => ReactNode
}

export function AnimatedCards({
  cards,
  rounded = false,
  className,
  mobileContainerClassName,
  desktopContainerClassName,
  cardClassName,
  initialBlur = 3,
  initialScale = 0.8,
  desktopHoverScale = 1.02,
  renderCardFooter,
}: AnimatedCardsProps) {
  const cardShapeClass = rounded ? "rounded-4xl" : "rounded-none"

  const defaultFooter = (card: CardItem) => (
    <div className="items-baseline p-2">
      <h1 className="text-start">{card.title}</h1>
      <p className="text-start text-sm">{card.description}</p>
    </div>
  )

  const renderFooter = renderCardFooter ?? defaultFooter

  return (
    <div
      className={cn(
        "mx-auto flex h-[calc(100svh-60px)] w-full items-center justify-center overflow-hidden",
        className
      )}
    >
      <div
        className={cn(
          "flex w-full snap-x snap-mandatory gap-6 overflow-x-auto px-6 py-10 [-ms-overflow-style:none] [scrollbar-width:none] md:hidden [&::-webkit-scrollbar]:hidden",
          mobileContainerClassName
        )}
      >
        {cards.map((card) => (
          <div
            key={`${card.title}-mobile`}
            className={cn(
              "flex h-84 w-64 shrink-0 snap-center flex-col justify-between border-2 p-2 text-2xl font-bold",
              cardShapeClass,
              cardClassName,
              card.className
            )}
          >
            {card.skeleton}
            {renderFooter(card)}
          </div>
        ))}
      </div>

      <div
        className={cn(
          "relative hidden h-84 w-264 shrink-0 origin-center md:block md:scale-[0.65] lg:scale-90 xl:scale-100",
          desktopContainerClassName
        )}
      >
        {cards.map((card) => (
          <motion.div
            key={`${card.title}-desktop`}
            className="absolute top-0 left-0"
            initial={{
              x: 400,
              y: 200,
              zIndex: 1,
              filter: `blur(${initialBlur}px)`,
              scale: initialScale,
              rotate: 0,
            }}
            animate={{
              x: card.config.x,
              y: card.config.y,
              zIndex: card.config.zIndex,
              filter: "blur(0px)",
              scale: 1,
              rotate: card.rotation,
            }}
            transition={{
              type: "spring",
              damping: 25,
              stiffness: 120,
            }}
          >
            <motion.button
              type="button"
              whileHover={{ scale: desktopHoverScale }}
              transition={{
                type: "spring",
                damping: 25,
                stiffness: 300,
              }}
              className={cn(
                "flex h-84 w-64 origin-center flex-col justify-between border-2 p-2 text-2xl font-bold",
                "[outline:1px_solid_transparent] backface-hidden transform-3d",
                cardShapeClass,
                cardClassName,
                card.className
              )}
            >
              {card.skeleton}
              {renderFooter(card)}
            </motion.button>
          </motion.div>
        ))}
      </div>
    </div>
  )
}

More from xcn

Browse all components in this library →