@markoradak/folder-card
Animated folder-style cards for React. FLIP spring expansion, 3D lid rotation, SVG notch masks, and a render props API.
npm install @markoradak/folder-card motionFeatures
FLIP Spring Animation
Cards spring from grid position to centered dialog using Framer Motion physics. Configurable stiffness and damping.
3D Lid Rotation
Perspective-correct 3D rotation on any edge. Bottom, top, left, right, or auto based on aspect ratio.
SVG Notch Mask
Folder-style tab cutouts at any corner or edge. Dynamic SVG masks with configurable concave and inverted radii.
Render Props API
Full control over card face, detail content, and tab via render props. No opinionated markup or styles imposed.
Stagger Animations
FolderCardItem wraps content for automatic staggered fade-in. Built-in Framer Motion variants included.
CSS Variable Theming
Override --fc-* variables for colors, radii, shadows, and transitions. Automatic dark mode via prefers-color-scheme.
Automatic hinge detection
With hingeSide="auto", the lid rotation axis adapts to the card's aspect ratio.
Add content to the card lid to make it taller. Once it becomes portrait, the hinge automatically switches from bottom to left.
8 notch positions
Place the folder tab at any corner or edge. Select a position to see the notch move.
Hinge sides
Four directions for lid rotation. Click each card to see it in action.
Content cards
Editorial layouts with hingeSide="left" and notchPosition="bottom-right".
Tune the physics
Each card uses a different spring configuration. Click to feel the difference.
Fade lid
With fadeLid, the lid fades out on open instead of staying visible during 3D rotation.
Code Examples
Basic usage
FolderCardGroup manages state. FolderCard takes renderLid and renderDetail. FolderCardItem adds stagger animations.
import {
FolderCardGroup,
FolderCard,
FolderCardItem,
} from "@markoradak/folder-card";
import "@markoradak/folder-card/styles";
function MyCards() {
return (
<FolderCardGroup>
<FolderCard
id="project-1"
renderLid={() => (
<div className="p-5">
<h3 className="font-semibold">Project Alpha</h3>
<p className="text-sm text-muted">Click to expand</p>
</div>
)}
renderDetail={(close) => (
<div className="p-8">
<h2 className="text-lg font-semibold">Project Alpha</h2>
<FolderCardItem>
<p>Stagger-animated content.</p>
</FolderCardItem>
<button onClick={close}>Close</button>
</div>
)}
/>
</FolderCardGroup>
);
}Tab notch
Add renderTab for a folder-style tab cutout. 8 positions available (corners and edges) with configurable radii.
<FolderCard
id="with-tab"
notchPosition="top-right"
renderLid={() => <CardFace />}
renderDetail={(close) => <CardDetail close={close} />}
renderTab={() => (
<div className="pr-5 pt-3 pb-2 pl-4">
<div className="flex size-8 items-center justify-center
rounded-full border border-border/40
group-hover:border-foreground/30">
<ArrowUpRight className="size-3.5 text-muted
group-hover:text-foreground" />
</div>
</div>
)}
/>Custom spring
Pass springConfig to FolderCardGroup for bouncy, smooth, or snappy animations. Adjust stiffness and damping.
<FolderCardGroup
springConfig={{ stiffness: 200, damping: 15 }}
>
<FolderCard
id="bouncy-card"
renderLid={() => <CardFace />}
renderDetail={(close) => <CardDetail close={close} />}
/>
</FolderCardGroup>Hinge side + notch position
Combine hingeSide and notchPosition for unique card orientations. The tab cutout and lid rotation work together.
<FolderCard
id="recipe-card"
hingeSide="left"
notchPosition="bottom-right"
renderLid={() => <RecipeFace />}
renderDetail={(close) => <RecipeDetail close={close} />}
renderTab={() => <TabIcon />}
/>CSS variable theming
Override --fc-* CSS custom properties for colors, radii, shadows, and transitions. Supports light and dark mode.
:root {
--fc-radius: 20px;
--fc-card-bg: #ffffff;
--fc-foreground: #0a0a0a;
--fc-border: rgba(0, 0, 0, 0.1);
--fc-muted: rgba(0, 0, 0, 0.45);
--fc-shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.08);
--fc-shadow-lg: 0 8px 30px rgba(0, 0, 0, 0.12);
}
@media (prefers-color-scheme: dark) {
:root {
--fc-card-bg: #111111;
--fc-foreground: #f5f5f5;
--fc-border: rgba(255, 255, 255, 0.06);
}
}