Folder Interaction

Note: The backdrop blur effect may not appear in this documentation preview due to MDX rendering limitations. However, when you install and use this component in your project, the blur effect will work correctly. View demo video → to see the full effect with blur.

folder-interaction.tsx
"use client";
import { motion } from "motion/react";
import { useState } from "react";

function FolderInteraction() {
  const [isOpen, setIsOpen] = useState(false);

  const pageVariants = {
    spring: { type: "spring" as const, duration: 0.6 },
  };

  return (
    <div className="w-full  flex justify-center items-center">
      <div
        onClick={() => setIsOpen(!isOpen)}
        className="w-80 h-52 relative wrapper"
      >
        <div
          className="folder relative w-[87.5%] mx-auto items-center h-full flex justify-center "
          style={{
            background: "#18151B",
            boxShadow:
              "0px 0px 15.699999809265137px 16px rgba(79, 73, 85, 0.30) inset",
            borderRadius: 10,
          }}
        >
          {[
            {
              initial: { rotate: -3, x: -38, y: 2 },
              open: { rotate: -8, x: -70, y: -55 },
              transition: {
                ...pageVariants.spring,
                bounce: 0.15,
                stiffness: 160,
                damping: 22,
              },
              className: "z-10 shadow-md",
            },
            {
              initial: { rotate: 0, x: 0, y: 0 },
              open: { rotate: 1, x: 2, y: -75 },
              transition: {
                ...pageVariants.spring,
                duration: 0.55,
                bounce: 0.12,
                stiffness: 190,
                damping: 24,
              },
              className: "z-20 shadow-lg",
            },
            {
              initial: { rotate: 3.5, x: 42, y: 1 },
              open: { rotate: 9, x: 75, y: -60 },
              transition: {
                ...pageVariants.spring,
                duration: 0.58,
                bounce: 0.17,
                stiffness: 170,
                damping: 21,
              },
              className: "z-10 shadow-md",
            },
          ].map((page, i) => (
            <motion.div
              key={i}
              initial={page.initial}
              animate={isOpen ? page.open : page.initial}
              transition={page.transition}
              className={`absolute top-2 w-32 h-fit rounded-xl ${page.className}`}
            >
              <Page />
            </motion.div>
          ))}
        </div>

        <motion.div
          animate={{ rotateX: isOpen ? -40 : 0 }}
          transition={{ type: "spring", duration: 0.5, bounce: 0.2 }}
          className="absolute -left-[1px] -right-[1px] -bottom-[1px] z-20 h-44 rounded-3xl origin-bottom flex justify-center items-center overflow-visible"
        >
          <svg
            className="w-full h-full overflow-visible"
            viewBox="0 0 235 121"
            fill="none"
            preserveAspectRatio="none"
            xmlns="http://www.w3.org/2000/svg"
          >
            <foreignObject x="-13" y="-13" width="262.4" height="148.4">
              <div
                style={{
                  backdropFilter: "blur(6.5px)",
                  clipPath: "url(#bgblur_0_1_106_clip_path)",
                  height: "100%",
                  width: "100%",
                }}
              ></div>
            </foreignObject>
            <path
              id="Vector"
              data-figma-bg-blur-radius="13"
              d="M104.615 0.350494L33.1297 0.838776C32.7542 0.841362 32.3825 0.881463 32.032 0.918854C31.6754 0.956907 31.3392 0.992086 31.0057 0.992096H31.0047C30.6871 0.99235 30.3673 0.962051 30.0272 0.929596C29.6927 0.897686 29.3384 0.863802 28.9803 0.866119L13.2693 0.967682H13.2527L13.2352 0.969635C13.1239 0.981406 13.0121 0.986674 12.9002 0.986237H9.91388C8.33299 0.958599 6.76052 1.22345 5.27423 1.76651H5.27325C4.33579 2.11246 3.48761 2.66213 2.7879 3.37393L2.49689 3.68839L2.492 3.69424C1.62667 4.73882 1.00023 5.96217 0.656067 7.27725C0.653324 7.28773 0.654065 7.29886 0.652161 7.30948C0.3098 8.62705 0.257231 10.0048 0.499817 11.3446L12.2147 114.399L12.2156 114.411L12.2176 114.423C12.6046 116.568 13.7287 118.508 15.3934 119.902C17.058 121.297 19.1572 122.056 21.3231 122.049V122.05H215.379C217.76 122.02 220.064 121.192 221.926 119.698V119.697C223.657 118.384 224.857 116.485 225.305 114.35L225.307 114.339L235.914 53.3798L235.968 53.1093L235.97 53.0985L235.971 53.0888C236.134 51.8978 236.044 50.685 235.705 49.5321C235.307 48.1669 234.63 46.9005 233.717 45.8144L233.383 45.4296C232.58 44.5553 231.614 43.8449 230.539 43.3398C229.311 42.7628 227.971 42.4685 226.616 42.4774H146.746C144.063 42.4705 141.423 41.8004 139.056 40.5263C136.691 39.2522 134.671 37.4127 133.175 35.1689L113.548 5.05948L113.544 5.05362L113.539 5.04776C112.545 3.65165 111.238 2.51062 109.722 1.72061C108.266 0.886502 106.627 0.422235 104.952 0.365143V0.364166L104.633 0.350494H104.615Z"
              fill="url(#paint0_linear_1_106)"
              fillOpacity="0.3"
              stroke="url(#paint1_linear_1_106)"
              strokeWidth="0.7"
            />
            <defs>
              <clipPath
                id="bgblur_0_1_106_clip_path"
                transform="translate(13 13)"
              >
                <path d="M104.615 0.350494L33.1297 0.838776C32.7542 0.841362 32.3825 0.881463 32.032 0.918854C31.6754 0.956907 31.3392 0.992086 31.0057 0.992096H31.0047C30.6871 0.99235 30.3673 0.962051 30.0272 0.929596C29.6927 0.897686 29.3384 0.863802 28.9803 0.866119L13.2693 0.967682H13.2527L13.2352 0.969635C13.1239 0.981406 13.0121 0.986674 12.9002 0.986237H9.91388C8.33299 0.958599 6.76052 1.22345 5.27423 1.76651H5.27325C4.33579 2.11246 3.48761 2.66213 2.7879 3.37393L2.49689 3.68839L2.492 3.69424C1.62667 4.73882 1.00023 5.96217 0.656067 7.27725C0.653324 7.28773 0.654065 7.29886 0.652161 7.30948C0.3098 8.62705 0.257231 10.0048 0.499817 11.3446L12.2147 114.399L12.2156 114.411L12.2176 114.423C12.6046 116.568 13.7287 118.508 15.3934 119.902C17.058 121.297 19.1572 122.056 21.3231 122.049V122.05H215.379C217.76 122.02 220.064 121.192 221.926 119.698V119.697C223.657 118.384 224.857 116.485 225.305 114.35L225.307 114.339L235.914 53.3798L235.968 53.1093L235.97 53.0985L235.971 53.0888C236.134 51.8978 236.044 50.685 235.705 49.5321C235.307 48.1669 234.63 46.9005 233.717 45.8144L233.383 45.4296C232.58 44.5553 231.614 43.8449 230.539 43.3398C229.311 42.7628 227.971 42.4685 226.616 42.4774H146.746C144.063 42.4705 141.423 41.8004 139.056 40.5263C136.691 39.2522 134.671 37.4127 133.175 35.1689L113.548 5.05948L113.544 5.05362L113.539 5.04776C112.545 3.65165 111.238 2.51062 109.722 1.72061C108.266 0.886502 106.627 0.422235 104.952 0.365143V0.364166L104.633 0.350494H104.615Z" />
              </clipPath>
              <linearGradient
                id="paint0_linear_1_106"
                x1="114.7"
                y1="0.700104"
                x2="114.7"
                y2="121.7"
                gradientUnits="userSpaceOnUse"
              >
                <stop stopColor="#2D2535" />
                <stop offset="1" stopColor="#2A2A2A" />
              </linearGradient>
              <linearGradient
                id="paint1_linear_1_106"
                x1="114.7"
                y1="0.700104"
                x2="114.7"
                y2="121.7"
                gradientUnits="userSpaceOnUse"
              >
                <stop stopColor="#424242" stopOpacity="0.04" />
                <stop offset="1" stopColor="#212121" stopOpacity="0.3" />
              </linearGradient>
            </defs>
          </svg>
        </motion.div>
      </div>
    </div>
  );
}

export default FolderInteraction;

const Page = () => (
  <div className="w-full h-full bg-gradient-to-b from-[#E8E7F0] to-[#DCDAE8] rounded-xl shadow-lg p-3 sm:p-4">
    <div className="flex flex-col gap-1.5 sm:gap-2">
      <div className="w-full h-1 sm:h-1.5 bg-[#CFCDE0] rounded-full" />
      {Array.from({ length: 8 }).map((_, i) => (
        <div key={i} className="flex gap-1.5 sm:gap-2">
          <div className="flex-1 h-1 sm:h-1.5 bg-[#CFCDE0] rounded-full" />
          <div className="flex-1 h-1 sm:h-1.5 bg-[#CFCDE0] rounded-full" />
        </div>
      ))}
    </div>
  </div>
);

Installation

npx shadcn@latest add "https://uselayouts.com/r/folder-interaction.json"

Install dependencies

npm install motion

Copy the code

Copy the code from the Code tab above into components/folder-interaction.tsx.

Update imports

Update the imports to match your project structure.

Usage

import FolderInteraction from "@/components/folder-interaction";

export default function Page() {
  return (
    <div className="flex items-center justify-center min-h-[400px] w-full">
      <FolderInteraction />
    </div>
  );
}

Features

  • 3D folder opening - Realistic folder flip animation with perspective
  • Document fan-out - Three documents spread out with staggered spring animations
  • Glassmorphism - Frosted glass effect on the folder front using backdrop blur
  • Click to toggle - Simple click interaction to open/close the folder
  • SVG folder design - Custom SVG folder shape with gradient fills

Important Note

The backdrop blur effect uses CSS backdrop-filter which may not render correctly in MDX documentation previews due to browser security restrictions with SVG foreignObject elements. When you install this component in your project, the blur effect will work perfectly.

For the best experience, check out the live demo to see the component with full blur effects.