Something new is coming.Join the waitlist

Folder

PreviousNext

Animated 3D folder that fans its files out on hover. Built with motion.

File 1
File 2
File 3
File 1
File 2
File 3

Installation

npx shadcn@latest add https://ui.srb.codes/r/folder.json

Usage

"use client";
 
import { Folder } from "@/components/folder";
 
export function Hero() {
  return <Folder size={56} />;
}

Patterns

Driving hover from a parent

The folder tracks its own hover state by default, but you can lift it up when the open animation should fire from a different element — for example, a pill that contains the folder plus a label.

"use client";
 
import * as React from "react";
 
import { Folder } from "@/components/folder";
 
export function FolderPill() {
  const [hovered, setHovered] = React.useState(false);
 
  return (
    <button
      type="button"
      onMouseEnter={() => setHovered(true)}
      onMouseLeave={() => setHovered(false)}
      className="inline-flex items-center gap-2 rounded-full border px-3 py-1"
    >
      <Folder size={24} isHovered={hovered} />
      <span>New components every week</span>
    </button>
  );
}

When you pass isHovered, the component stops listening to its own pointer events and follows the prop instead.

Custom files

Pass any three (or more) image URLs and they'll fan out in the order given. Smaller folders look best with smaller images, so keep aspect ratios uniform.

<Folder
  size={64}
  files={[
    "/screenshots/inbox.png",
    "/screenshots/calendar.png",
    "/screenshots/notes.png"
  ]}
/>

Icon on the front face

Drop any React node into icon to label the folder — typically a Lucide icon, but a string, an emoji, or an inline SVG all work. Lucide-style SVGs are auto-sized to size-5 and tinted to match the folder; pass a node with explicit size/color classes to override.

import { ImageIcon } from "lucide-react";
 
<Folder size={56} icon={<ImageIcon />} />;

The icon is mounted inside the front face, so it tilts and scales with the open animation — it shrinks slightly as the folder fans open, then settles back when the cursor leaves. The wrapper is pointer-events-none and aria-hidden, so the icon is purely decorative and never steals hover from the folder itself.

Props

PropTypeDefaultDescription
sizenumber62Pixel height of the rendered folder. The component scales the natural 72×62 artwork uniformly so it stays crisp at any size.
isHoveredbooleanForce the open/closed state from outside. When omitted, the folder tracks its own pointer hover.
filesstring[]three picsum placeholdersImage URLs that pop out of the folder. The first three are positioned to fan out on hover; additional entries stack behind the leading file.
iconReact.ReactNodeOptional icon (or any node) rendered centered on the folder's front face. Lucide SVGs auto-size to size-5 and tint white.
classNamestringForwarded to the outer wrapper.

Sizing

The artwork has a natural footprint of 72×62 pixels (a 54-tall body plus an 8-tall tab on top). size is interpreted as a target height in pixels and the whole composition scales uniformly — so passing size={24} renders a folder that occupies roughly 28×24 px and size={120} gives you a 140×120 hero element. The CSS box that the folder reserves matches the scaled dimensions, so it lays out predictably inside flex and inline-flex parents.

Theming

The folder uses Tailwind's blue palette directly (from-blue-300/400/500 with a darker dark: ramp) rather than CSS variables, because it's intentionally decorative — the goal is to look the same across themes, not to follow the active accent color. To recolor, fork the component and swap the from-*/to-* classes; everything else (shadows, ring, file thumbnails) is theme-neutral.