---
title: 3d Image Pageflip
description: An interactive 3D book and magazine page flip component featuring dynamic spine shifting, realistic lighting crease shadows, hover peeking, and leaf stacking.
---
## Installation
CLI
Manual
```bash
npx lightswind@latest add 3d-image-pageflip
```
Copy and paste the following code into your project.
```tsx
"use client";
import React, { useState, useEffect, useCallback, CSSProperties, forwardRef, useImperativeHandle } from "react";
import { cn } from "@/components/lib/utils";
import { ChevronLeft, ChevronRight, RotateCcw } from "lucide-react";
export interface PageFlipLeaf {
id?: string | number;
frontImage: string;
backImage: string;
frontTitle?: string;
backTitle?: string;
frontSubtitle?: string;
backSubtitle?: string;
frontBadge?: string;
backBadge?: string;
}
export interface ThreeDImagePageflipProps {
/** Array of page leaves, each containing front and back images & metadata */
pages?: PageFlipLeaf[];
/** Default turned page count (0 = closed book on cover) */
defaultTurnedIndex?: number;
/** Controlled turned page count */
turnedIndex?: number;
/** Callback fired when page flip changes */
onPageChange?: (turnedCount: number, totalLeaves: number) => void;
/** Width of a single page in pixels (default: 230) */
pageWidth?: number;
/** Height of a single page in pixels (default: 330) */
pageHeight?: number;
/** 3D perspective depth in pixels (default: 1300) */
perspective?: number;
/** Maximum hover peek angle in degrees (default: 14) */
peekAngle?: number;
/** Total turn angle in degrees (default: 180) */
turnAngle?: number;
/** Transition flip animation duration in seconds (default: 0.65) */
duration?: number;
/** Easing curve for flip animation (default: "cubic-bezier(0.4, 0, 0.2, 1)") */
easing?: string;
/** Shadow intensity factor (0.0 to 1.0, default: 0.45) */
shadowIntensity?: number;
/** Dynamically shift spine horizontally when book is open to center the 2-page spread (default: true) */
spineShift?: boolean;
/** Border radius for pages (default: "10px") */
radius?: string | number;
/** Enable page numbering tags (default: true) */
showPageNumbers?: boolean;
/** Enable outer book leather spine binding (default: true) */
showSpineBinding?: boolean;
/** Accent glow color for active elements (default: "#00F5FF") */
accentColor?: string;
/** Enable automatic page flipping (default: false) */
autoplay?: boolean;
/** Autoplay interval in milliseconds (default: 3500) */
autoplayInterval?: number;
/** Pause autoplay on hover (default: true) */
pauseOnHover?: boolean;
/** Enable interactive click on pages to flip (default: true) */
interactive?: boolean;
/** Enable navigation buttons (default: true) */
showControls?: boolean;
/** Optional container class name */
className?: string;
/** Optional container inline style */
style?: CSSProperties;
}
export interface ThreeDImagePageflipHandle {
next: () => void;
prev: () => void;
reset: () => void;
goTo: (index: number) => void;
getTurnedCount: () => number;
getTotalLeaves: () => number;
}
const DEFAULT_PAGES: PageFlipLeaf[] = [
{
id: 1,
frontImage: "https://images.unsplash.com/photo-1600585154340-be6161a56a0c?auto=format&fit=crop&w=800&q=80",
backImage: "https://images.unsplash.com/photo-1512917774080-9991f1c4c750?auto=format&fit=crop&w=800&q=80",
frontTitle: "Villa Solarium",
frontSubtitle: "Horizon Pool & Architecture",
frontBadge: "Cover",
backTitle: "Minimal Horizon",
backSubtitle: "Geometric Water Pavilion",
backBadge: "Plate 01",
},
{
id: 2,
frontImage: "https://images.unsplash.com/photo-1513694203232-719a280e022f?auto=format&fit=crop&w=800&q=80",
backImage: "https://images.unsplash.com/photo-1486406146926-c627a92ad1ab?auto=format&fit=crop&w=800&q=80",
frontTitle: "Apex Structure",
frontSubtitle: "Parametric Glass Facade",
frontBadge: "Plate 02",
backTitle: "Glass Skyline",
backSubtitle: "Monolith Metropolitan Tower",
backBadge: "Plate 03",
},
{
id: 3,
frontImage: "https://images.unsplash.com/photo-1506744038136-46273834b3fb?auto=format&fit=crop&w=800&q=80",
backImage: "https://images.unsplash.com/photo-1511818966892-d7d671e672a2?auto=format&fit=crop&w=800&q=80",
frontTitle: "Emerald Cascade",
frontSubtitle: "Alpine Mist & Forest Ridge",
frontBadge: "Plate 04",
backTitle: "Nordic Pavilion",
backSubtitle: "Natural Timber Canopy",
backBadge: "Plate 05",
},
{
id: 4,
frontImage: "https://images.unsplash.com/photo-1518780664697-55e3ad937233?auto=format&fit=crop&w=800&q=80",
backImage: "https://images.unsplash.com/photo-1507652313519-d4e9174996dd?auto=format&fit=crop&w=800&q=80",
frontTitle: "Warm Solarium",
frontSubtitle: "Sunlight & Interior Loft",
frontBadge: "Plate 06",
backTitle: "Zen Courtyard",
backSubtitle: "Brutalist Stone Water Feature",
backBadge: "Plate 07",
},
{
id: 5,
frontImage: "https://images.unsplash.com/photo-1486325212027-8081e485255e?auto=format&fit=crop&w=800&q=80",
backImage: "https://images.unsplash.com/photo-1479839672679-a46483c0e7c8?auto=format&fit=crop&w=800&q=80",
frontTitle: "Cyber Metropolis",
frontSubtitle: "Urban Geometric Skyline",
frontBadge: "Plate 08",
backTitle: "Monolith Curve",
backSubtitle: "Brutalist Concrete Finish",
backBadge: "Endplate",
},
];
export const ThreeDImagePageflip = forwardRef(({
pages = DEFAULT_PAGES,
defaultTurnedIndex = 0,
turnedIndex: controlledTurnedIndex,
onPageChange,
pageWidth = 230,
pageHeight = 330,
perspective = 1300,
peekAngle = 14,
turnAngle = 180,
duration = 0.65,
easing = "cubic-bezier(0.4, 0, 0.2, 1)",
shadowIntensity = 0.45,
spineShift = true,
radius = "10px",
showPageNumbers = true,
showSpineBinding = true,
accentColor = "#00F5FF",
autoplay = false,
autoplayInterval = 3500,
pauseOnHover = true,
interactive = true,
showControls = true,
className,
style,
}, ref) => {
const [internalTurned, setInternalTurned] = useState(defaultTurnedIndex);
const [isHovered, setIsHovered] = useState(false);
const [peekingIndex, setPeekingIndex] = useState(null);
const totalLeaves = pages.length;
const currentTurned = controlledTurnedIndex !== undefined ? controlledTurnedIndex : internalTurned;
const isOpen = currentTurned > 0 && currentTurned < totalLeaves;
const parsedRadius = typeof radius === "number" ? `${radius}px` : radius;
const setTurned = useCallback((newCount: number) => {
const clamped = Math.max(0, Math.min(newCount, totalLeaves));
if (controlledTurnedIndex === undefined) {
setInternalTurned(clamped);
}
if (onPageChange) {
onPageChange(clamped, totalLeaves);
}
}, [controlledTurnedIndex, totalLeaves, onPageChange]);
const flipNext = useCallback(() => {
if (currentTurned < totalLeaves) {
setTurned(currentTurned + 1);
}
}, [currentTurned, totalLeaves, setTurned]);
const flipPrev = useCallback(() => {
if (currentTurned > 0) {
setTurned(currentTurned - 1);
}
}, [currentTurned, setTurned]);
const resetBook = useCallback(() => {
setTurned(0);
}, [setTurned]);
useImperativeHandle(ref, () => ({
next: flipNext,
prev: flipPrev,
reset: resetBook,
goTo: (idx) => setTurned(idx),
getTurnedCount: () => currentTurned,
getTotalLeaves: () => totalLeaves,
}));
// Autoplay Timer
useEffect(() => {
if (!autoplay || (pauseOnHover && isHovered) || totalLeaves <= 1) return;
const timer = setInterval(() => {
setInternalTurned((prev) => (prev >= totalLeaves ? 0 : prev + 1));
}, autoplayInterval);
return () => clearInterval(timer);
}, [autoplay, autoplayInterval, pauseOnHover, isHovered, totalLeaves]);
// Keyboard Navigation
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === "ArrowRight") flipNext();
if (e.key === "ArrowLeft") flipPrev();
};
window.addEventListener("keydown", handleKeyDown);
return () => window.removeEventListener("keydown", handleKeyDown);
}, [flipNext, flipPrev]);
const handleLeafClick = (index: number) => {
if (!interactive) return;
if (index === currentTurned) {
// Click unturned top page -> flip forward
flipNext();
} else if (index === currentTurned - 1) {
// Click turned top left page -> flip backward
flipPrev();
}
};
return (
setIsHovered(true)}
onMouseLeave={() => {
setIsHovered(false);
setPeekingIndex(null);
}}
>
{/* 3D Book Viewport Stage */}
{/* 3D Book Container */}
{/* Spine Shadow & Binding Crease */}
{showSpineBinding && (
)}
{/* Ground Ambience Drop Shadow underneath the book */}
{/* Book Leaves Stacking Loop */}
{pages.map((leaf, index) => {
const isTurned = index < currentTurned;
const isCanPeek = index === currentTurned;
const isPeeking = peekingIndex === index;
// Calculate Z-Index: turned leaves stack forward on left, unturned leaves stack backward on right
const zIndex = isTurned ? index + 1 : totalLeaves - index;
// Rotation Angle
let leafRotation = isTurned ? -turnAngle : 0;
if (!isTurned && isPeeking) {
leafRotation = -peekAngle;
}
return (
handleLeafClick(index)}
onMouseEnter={() => {
if (isCanPeek) setPeekingIndex(index);
}}
onMouseLeave={() => {
if (peekingIndex === index) setPeekingIndex(null);
}}
className={cn(
"absolute inset-0 origin-left cursor-pointer",
interactive ? "cursor-pointer" : "pointer-events-none"
)}
style={{
transformStyle: "preserve-3d",
transition: `transform ${duration}s ${easing}`,
transform: `rotateY(${leafRotation}deg)`,
zIndex,
borderRadius: parsedRadius,
}}
>
{/* FRONT FACE (Visible when page is on the right) */}

{/* Spine crease shadow overlay for 3D depth */}
{/* Bottom Vignette & Metadata */}
{leaf.frontBadge && (
{leaf.frontBadge}
)}
{showPageNumbers && (
{index * 2 + 1}
)}
{leaf.frontTitle && (
{leaf.frontTitle}
)}
{leaf.frontSubtitle && (
{leaf.frontSubtitle}
)}
{/* BACK FACE (Visible when page is turned to the left) */}

{/* Spine crease shadow overlay for turned back-face */}
{/* Bottom Vignette & Metadata */}
{leaf.backBadge && (
{leaf.backBadge}
)}
{showPageNumbers && (
{index * 2 + 2}
)}
{leaf.backTitle && (
{leaf.backTitle}
)}
{leaf.backSubtitle && (
{leaf.backSubtitle}
)}
);
})}
{/* Book Controls & Page Progress Toolbar */}
{showControls && (
{currentTurned}
/
{totalLeaves} leaves
)}
);
});
ThreeDImagePageflip.displayName = "ThreeDImagePageflip";
export default ThreeDImagePageflip;
```
## Usage
```tsx
import ThreeDImagePageflip, { PageFlipLeaf } from "@/components/lightswind/3d-image-pageflip";
```
```tsx
import ThreeDImagePageflip, { PageFlipLeaf } from "@/components/lightswind/3d-image-pageflip";
const pages: PageFlipLeaf[] = [
{
frontImage: "https://images.unsplash.com/photo-1600585154340-be6161a56a0c?auto=format&fit=crop&w=800&q=80",
backImage: "https://images.unsplash.com/photo-1512917774080-9991f1c4c750?auto=format&fit=crop&w=800&q=80",
frontTitle: "Villa Solarium",
frontSubtitle: "Horizon Pool & Architecture",
frontBadge: "Cover",
backTitle: "Minimal Horizon",
backSubtitle: "Geometric Water Pavilion",
backBadge: "Plate 01",
},
{
frontImage: "https://images.unsplash.com/photo-1513694203232-719a280e022f?auto=format&fit=crop&w=800&q=80",
backImage: "https://images.unsplash.com/photo-1486406146926-c627a92ad1ab?auto=format&fit=crop&w=800&q=80",
frontTitle: "Apex Structure",
frontSubtitle: "Parametric Glass Facade",
frontBadge: "Plate 02",
backTitle: "Glass Skyline",
backSubtitle: "Monolith Tower",
backBadge: "Plate 03",
},
];
export default function Example() {
return (
);
}
```