makeui
DocsComponents

© 2026 makeui. Open source, MIT licensed.

DocsComponentsGitHub

Folder Flow

An animated folder icon with papers that rise smoothly on hover.

Preview

Installation

pnpm dlx shadcn@latest add https://makeui.dev/r/folder-flow.json

Dependencies

  • motion
  • lucide-react

Interaction

Hover over the folder to animate the papers rising out of it. Move the pointer away to smoothly return the papers to their original position. It also opens on keyboard focus and when tapped on touch screens.

Props

PropTypeDescription
classNamestringExtra classes merged onto the root element.

How to use

import FolderFlow from "@/components/ui/folder-flow"

export function Example() {
  return <FolderFlow />
}

Source code

components/ui/folder-flow.tsx
"use client"

import { motion, type Variants } from "motion/react"
import { FileText, Image as ImageIcon, Music } from "lucide-react"
import { cn } from "@/lib/utils"

const spring = { type: "spring", stiffness: 260, damping: 22 } as const

const papers = [
  {
    Icon: ImageIcon,
    rest: { rotate: -4, x: -14, y: 0 },
    hover: { rotate: -12, x: -34, y: -46 },
  },
  { Icon: FileText, rest: { rotate: 0, x: 0, y: 0 }, hover: { rotate: 0, x: 0, y: -58 } },
  { Icon: Music, rest: { rotate: 4, x: 14, y: 0 }, hover: { rotate: 12, x: 34, y: -46 } },
]

const front: Variants = {
  rest: { rotateX: 0 },
  hover: { rotateX: -22, transition: spring },
}

export default function FolderFlow({ className }: { className?: string }) {
  return (
    <motion.div
      initial="rest"
      animate="rest"
      whileHover="hover"
      whileFocus="hover"
      whileTap="hover"
      tabIndex={0}
      aria-label="Folder"
      className={cn(
        "relative mx-auto h-36 w-44 cursor-pointer rounded-xl outline-none [perspective:700px]",
        className,
      )}
    >
      {/* back panel with tab */}
      <div className="absolute inset-x-0 bottom-0 h-28 rounded-xl bg-foreground/15">
        <div className="absolute -top-3 left-0 h-4 w-16 rounded-t-lg bg-foreground/15" />
      </div>

      {papers.map(({ Icon, rest, hover }, i) => (
        <motion.div
          key={i}
          variants={{
            rest: { ...rest, transition: spring },
            hover: { ...hover, transition: { ...spring, delay: i * 0.03 } },
          }}
          className="absolute inset-x-6 bottom-3 grid h-24 place-items-center rounded-md border border-border bg-card shadow-sm"
          style={{ zIndex: i === 1 ? 2 : 1 }}
        >
          <Icon className="size-5 text-muted-foreground" />
          <span className="absolute inset-x-3 top-3 h-1 rounded-full bg-muted" />
          <span className="absolute inset-x-3 top-6 h-1 w-1/2 rounded-full bg-muted" />
        </motion.div>
      ))}

      {/* front panel */}
      <motion.div
        variants={front}
        style={{ transformOrigin: "bottom" }}
        className="absolute inset-x-0 bottom-0 z-10 h-24 rounded-xl border border-border/60 bg-linear-to-b from-foreground/30 to-foreground/20 backdrop-blur-sm"
      />
    </motion.div>
  )
}

On this page