UI Components Easy
Stack Cards
Overlapping card stack UI — hover fans out the cards, click rotates through them. Used for layered content previews.
Open in Lab
MCP
css
Targets: HTML
Code
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: Inter, system-ui, sans-serif;
background: #050910;
color: #f2f6ff;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 2rem;
}
.demo {
width: 100%;
max-width: 640px;
}
.demo-title {
font-size: 1.5rem;
font-weight: 800;
margin-bottom: 0.375rem;
}
.demo-sub {
color: #475569;
font-size: 0.875rem;
margin-bottom: 2.5rem;
}
.section {
margin-bottom: 3rem;
}
.section-label {
font-size: 0.72rem;
font-weight: 600;
letter-spacing: 0.08em;
text-transform: uppercase;
color: #475569;
margin-bottom: 1.25rem;
}
/* ── Stack wrapper ── */
.stack {
position: relative;
width: 260px;
height: 160px;
cursor: pointer;
outline: none;
}
/* ── Card base ── */
.stack__card {
position: absolute;
inset: 0;
border-radius: 14px;
transition: transform 0.35s cubic-bezier(0.34, 1.56, 0.64, 1), box-shadow 0.35s ease;
will-change: transform;
}
/* ── Default stacked offsets (back → front = first → last child) ── */
.stack__card:nth-child(4) {
transform: translateY(-18px) scale(0.88);
z-index: 1;
}
.stack__card:nth-child(3) {
transform: translateY(-10px) scale(0.93);
z-index: 2;
}
.stack__card:nth-child(2) {
transform: translateY(-4px) scale(0.97);
z-index: 3;
}
.stack__card:nth-child(1) {
transform: translateY(0) scale(1);
z-index: 4;
}
/* ── Hover fan-out ── */
.stack:hover .stack__card:nth-child(4) {
transform: rotate(-18deg) translateX(-90px) translateY(-8px);
}
.stack:hover .stack__card:nth-child(3) {
transform: rotate(-7deg) translateX(-32px) translateY(-4px);
}
.stack:hover .stack__card:nth-child(2) {
transform: rotate(5deg) translateX(18px) translateY(-2px);
}
.stack:hover .stack__card:nth-child(1) {
transform: rotate(16deg) translateX(72px) translateY(2px);
}
/* ── Cycle animation ── */
.stack__card.cycling {
animation: cycle-out 0.4s cubic-bezier(0.55, 0, 1, 0.45) forwards;
z-index: 10;
}
@keyframes cycle-out {
0% {
transform: translateY(0) scale(1);
opacity: 1;
}
40% {
transform: translateY(-40px) scale(1.04);
opacity: 1;
}
100% {
transform: translateY(20px) scale(0.87);
opacity: 0;
}
}
/* ── Color deck card colors ── */
.stack__card--1 {
background: linear-gradient(135deg, #1e40af, #3b82f6);
}
.stack__card--2 {
background: linear-gradient(135deg, #065f46, #10b981);
}
.stack__card--3 {
background: linear-gradient(135deg, #7c2d12, #f97316);
}
.stack__card--4 {
background: linear-gradient(135deg, #4c1d95, #8b5cf6);
}
/* ── Glass deck override ── */
.stack--glass .stack__card {
background: rgba(255, 255, 255, 0.04);
border: 1px solid rgba(255, 255, 255, 0.1);
backdrop-filter: blur(8px);
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.4);
}
/* ── Card inner ── */
.card-inner {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: flex-end;
padding: 1rem 1.25rem;
}
.card-icon {
font-size: 1.5rem;
margin-bottom: 0.25rem;
}
.card-label {
font-size: 0.7rem;
font-weight: 600;
letter-spacing: 0.06em;
text-transform: uppercase;
color: rgba(255, 255, 255, 0.55);
margin-bottom: 0.15rem;
}
.card-value {
font-size: 0.9rem;
font-weight: 600;
color: rgba(255, 255, 255, 0.9);
}<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Stack Cards</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div class="demo">
<h1 class="demo-title">Stack Cards</h1>
<p class="demo-sub">Hover to fan out · click to cycle through the deck.</p>
<!-- Stack A — colored cards -->
<section class="section">
<p class="section-label">Color deck</p>
<div class="stack" role="group" aria-label="Card stack, click to cycle" tabindex="0">
<div class="stack__card stack__card--1">
<div class="card-inner">
<p class="card-label">Design</p>
<p class="card-value">Figma components</p>
</div>
</div>
<div class="stack__card stack__card--2">
<div class="card-inner">
<p class="card-label">Frontend</p>
<p class="card-value">Astro + Tailwind</p>
</div>
</div>
<div class="stack__card stack__card--3">
<div class="card-inner">
<p class="card-label">Backend</p>
<p class="card-value">Hono + Workers</p>
</div>
</div>
<div class="stack__card stack__card--4">
<div class="card-inner">
<p class="card-label">Deploy</p>
<p class="card-value">Cloudflare Pages</p>
</div>
</div>
</div>
</section>
<!-- Stack B — dark glassmorphism cards -->
<section class="section">
<p class="section-label">Glass deck</p>
<div class="stack stack--glass" role="group" aria-label="Glass card stack, click to cycle" tabindex="0">
<div class="stack__card stack__card--1">
<div class="card-inner">
<span class="card-icon">🚀</span>
<p class="card-label">Launch</p>
</div>
</div>
<div class="stack__card stack__card--2">
<div class="card-inner">
<span class="card-icon">✦</span>
<p class="card-label">Iterate</p>
</div>
</div>
<div class="stack__card stack__card--3">
<div class="card-inner">
<span class="card-icon">📈</span>
<p class="card-label">Scale</p>
</div>
</div>
<div class="stack__card stack__card--4">
<div class="card-inner">
<span class="card-icon">💎</span>
<p class="card-label">Ship</p>
</div>
</div>
</div>
</section>
</div>
<script>
// Click-to-cycle: move the last card to first position
document.querySelectorAll(".stack").forEach((stack) => {
function cycle() {
const cards = [...stack.querySelectorAll(".stack__card")];
if (cards.length < 2) return;
const last = cards[cards.length - 1];
last.classList.add("cycling");
last.addEventListener("animationend", () => {
last.classList.remove("cycling");
stack.prepend(last);
}, { once: true });
}
stack.addEventListener("click", cycle);
stack.addEventListener("keydown", (e) => {
if (e.key === "Enter" || e.key === " ") { e.preventDefault(); cycle(); }
});
});
</script>
</body>
</html>Stack Cards
Layered card deck with hover fan-out and click-to-cycle interactions.
Behavior
- Default — cards stack with slight vertical offset and scale reduction per layer
- Hover — cards fan out using
rotatetransforms, revealing all cards in the stack - Click — the bottom card moves to the top with a flip animation
Implementation
Pure CSS for the stack and fan-out; a single data-active index on the wrapper handles click cycling via CSS sibling combinators. No JavaScript required for the hover fan — only a minimal <script> is included for the click-to-cycle behavior to update the wrapper class.