Stackbits
Components > File Stack

File Stack

FileStack is a modern, animated component designed to display files, images, or media in an interactive, stacked layout. With smooth hover effects, subtle floating animations, and a 3D perspective, FileStack brings a dynamic feel to your UI. Perfect for portfolios, media galleries, or product showcases, it enhances engagement by revealing file details on hover. Built with Next.js, Tailwind CSS, and Framer Motion, FileStack ensures seamless performance while keeping your design sleek and intuitive. Whether you're displaying documents, images, or featured content, FileStack makes it visually stunning and user-friendly. 🚀

Preview

Calm Waters
Snowy Peaks
Sunset Bliss
Cultural Heritage
Modern Minimalism
Calm Waters
Snowy Peaks
Sunset Bliss
Cultural Heritage
Modern Minimalism
Serene Lake
Golden Hour
Coastal Vibes
Night Lights
Rustic Charm
Blooming Meadows
Mystic Mountains
Abstract Art

Follow below steps 👇🏻

Install dependencies

1
npm i framer-motion tailwindcss-animate

Component

Create a file file-stack.tsx in your components folder and paste this code

1
'use client';
2
3
import Image from 'next/image';
4
import React, { useState } from 'react';
5
import { motion, AnimatePresence } from 'framer-motion';
6
7
type FileStackItem = {
8
title: string;
9
image: string;
10
description: string;
11
link?: string; // Optional link for clickable items
12
};
13
14
interface FileStackProps {
15
items: FileStackItem[];
16
spacing?: number; // Customizable spacing between items
17
}
18
19
const FileStack = ({ items, spacing = 10 }: FileStackProps) => {
20
const [isHovered, setIsHovered] = useState<number | null>(null);
21
const [isLoading, setIsLoading] = useState<{ [key: string]: boolean }>({});
22
23
// Subtle floating animation
24
const floatingAnimation = {
25
y: [0, -5, 0],
26
transition: {
27
duration: 4,
28
repeat: Infinity,
29
ease: 'easeInOut'
30
}
31
};
32
33
// Calculate consistent spacing value for each card
34
const getCardSpacing = (index: number) => {
35
if (index === 0) return 0;
36
return `-${spacing}rem`;
37
};
38
39
return (
40
<div className="h-full w-full relative group">
41
{/* Gradient Overlay with Text */}
42
<div className="h-full w-full bg-gradient-to-b from-[#000000da] pointer-events-none to-transparent rounded-lg absolute z-10 flex flex-col">
43
<AnimatePresence>
44
{isHovered !== null && (
45
<motion.div
46
key={isHovered + 'text'}
47
initial={{ opacity: 0, y: 100 }}
48
animate={{ opacity: 1, y: 0, transition: { delay: 0.2 } }}
49
exit={{ opacity: 0, y: -100 }}
50
transition={{ duration: 0.2 }}
51
className="flex flex-col items-center justify-center mt-2 p-4"
52
>
53
<motion.h2
54
className="text-center font-bold text-2xl mb-2"
55
animate={{ scale: [1, 1.02, 1] }}
56
transition={{ duration: 1, repeat: Infinity }}
57
>
58
{items[isHovered].title}
59
</motion.h2>
60
<p className="text-center text-neutral-300 max-w-[500px] line-clamp-2">
61
{items[isHovered].description}
62
</p>
63
</motion.div>
64
)}
65
</AnimatePresence>
66
</div>
67
68
{/* Main FileStack Content */}
69
<div
70
style={{ perspective: '1000px' }}
71
className="flex flex-col h-full w-full items-center overflow-y-auto relative pt-[30%]"
72
>
73
{items.map((item, i) => (
74
<motion.div
75
key={i}
76
onMouseEnter={() => setIsHovered(i)}
77
onMouseLeave={() => setIsHovered(null)}
78
initial={{
79
rotateX: -50,
80
marginTop: getCardSpacing(i)
81
}}
82
animate={!isHovered ? floatingAnimation : undefined}
83
whileHover={{
84
rotateX: 0,
85
marginBottom: '7rem',
86
scale: 1.02,
87
boxShadow:
88
'0 0 30px 5px rgba(255, 255, 255, 0.2), inset 0 0 15px rgba(255, 255, 255, 0.1)',
89
transition: { duration: 0.3 }
90
}}
91
className={`
92
flex relative rounded-md flex-col items-center
93
max-w-[500px] justify-center bg-neutral-800 p-2
94
${item.link ? 'cursor-pointer' : ''}
95
transition-shadow duration-300
96
hover:shadow-xl hover:shadow-neutral-900/20
97
`}
98
onClick={() => item.link && window.open(item.link, '_blank')}
99
>
100
{/* FileStack Edge Detail */}
101
<div className="h-4 w-20 bg-neutral-800 rounded-ss-md rounded-se-md absolute -top-2" />
102
103
{/* Loading State */}
104
{isLoading[item.image] && (
105
<div className="absolute inset-0 flex items-center justify-center bg-neutral-800/50">
106
<div className="animate-spin rounded-full h-8 w-8 border-t-2 border-white" />
107
</div>
108
)}
109
110
{/* Image */}
111
<div className="relative w-full overflow-hidden rounded-sm">
112
<Image
113
src={item.image}
114
alt={item.title}
115
height={400}
116
width={400}
117
className="object-cover h-full max-w-[350px] transition-all duration-300"
118
onLoadingComplete={() => setIsLoading((prev) => ({ ...prev, [item.image]: false }))}
119
onLoadStart={() => setIsLoading((prev) => ({ ...prev, [item.image]: true }))}
120
priority={i < 2}
121
/>
122
</div>
123
124
{/* Hover Indicator */}
125
<motion.div
126
className="absolute bottom-0 left-1/2 -translate-x-1/2 w-1/3 h-1 bg-white/50 rounded-full"
127
initial={{ scaleX: 0 }}
128
whileHover={{ scaleX: 1 }}
129
/>
130
</motion.div>
131
))}
132
</div>
133
</div>
134
);
135
};
136
137
export default FileStack;

Usage

1
<FileStack
2
items={[
3
{
4
title: 'Urban Skyline',
5
image:
6
'https://images.unsplash.com/photo-1718563552473-2d97b224e801?w=900&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxmZWF0dXJlZC1waG90b3MtZmVlZHw1fHx8ZW58MHx8fHx8',
7
description:
8
'A breathtaking view of a modern cityscape with towering skyscrapers illuminated at dusk.'
9
},
10
{
11
title: 'Mountain Retreat',
12
image:
13
'https://images.unsplash.com/photo-1735317461815-1a0ba64e9a56?w=900&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxmZWF0dXJlZC1waG90b3MtZmVlZHwyMXx8fGVufDB8fHx8fA%3D%3D',
14
description:
15
'A serene cabin nestled in the heart of towering mountains, perfect for a peaceful getaway.'
16
},
17
{
18
title: 'Forest Wander',
19
image:
20
'https://images.unsplash.com/photo-1502082553048-f009c37129b9?ixlib=rb-4.0.3&auto=format&fit=crop&w=400&q=80',
21
description:
22
'A misty trail winding through a dense, enchanting forest filled with lush greenery.'
23
},
24
{
25
title: 'Serene Lake',
26
image:
27
'https://images.unsplash.com/photo-1504384308090-c894fdcc538d?ixlib=rb-4.0.3&auto=format&fit=crop&w=400&q=80',
28
description:
29
'A tranquil lake reflecting the golden hues of the sunset, surrounded by peaceful nature.'
30
},
31
{
32
title: 'Golden Hour',
33
image:
34
'https://images.unsplash.com/photo-1507525428034-b723cf961d3e?ixlib=rb-4.0.3&auto=format&fit=crop&w=400&q=80',
35
description:
36
'A mesmerizing sunset casting a warm glow over the ocean, creating a dreamlike atmosphere.'
37
},
38
{
39
title: 'Coastal Vibes',
40
image:
41
'https://images.unsplash.com/photo-1493558103817-58b2924bce98?ixlib=rb-4.0.3&auto=format&fit=crop&w=400&q=80',
42
description:
43
'Crystal-clear waves crashing against a sandy shore, offering a perfect beach escape.'
44
},
45
{
46
title: 'Night Lights',
47
image:
48
'https://images.unsplash.com/photo-1502933691298-84fc14542831?w=900&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8Mnx8d2F0ZXIlMjBzcG9ydHxlbnwwfHwwfHx8MA%3D%3D',
49
description:
50
'A dazzling city skyline at night, with vibrant lights illuminating the urban landscape.'
51
},
52
{
53
title: 'Rustic Charm',
54
image:
55
'https://images.unsplash.com/photo-1517248135467-4c7edcad34c4?ixlib=rb-4.0.3&auto=format&fit=crop&w=400&q=80',
56
description:
57
'A cozy wooden cabin with a warm, inviting atmosphere set in a countryside setting.'
58
}
59
]}
60
></FileStack>

⭐️ Got a question or feedback?
Feel free to reach out!