SolutionsCase StudiesAI ToolsResourcesAboutContact
Get Your Free AI Automation Audit Book a Free 30-Minute Strategy Call
Playground~/frontend Open editor

~/css/stacked-cards

7 CSS Stacked Cards

5 Pure CSS2 with JSPublished 2026-10-05Free · no sign-up

Seven ways to stack cards: a fanned hand of cards, a deck you can flip through, sticky cards that pile up as you scroll, a pile that spreads into a row, notifications that collapse into a stack, a pile of instant photos and a pricing card with layers behind it.

01. Fanned Card Stack

StackHover

Three cards sit on top of each other, slightly rotated like a held hand of cards, and fan out further on hover or focus. Each card's rotation comes from a custom property, so changing the angle in one place changes the whole fan.

View code
<div class="sc1" tabindex="0" aria-label="Three colour themes: Ember, Violet, Lagoon"><div style="--r:-8deg;--c:#c2410c">Ember</div><div style="--r:0deg;--c:#6d28d9">Violet</div><div style="--r:8deg;--c:#0e7490">Lagoon</div></div>
.sc1 { position: relative; display: grid; width: 180px; height: 220px; font: 800 18px Inter, system-ui, sans-serif; }
.sc1 > div { grid-area: 1 / 1; display: grid; align-items: end; padding: 16px; border-radius: 16px; background: var(--c); color: #fff; transform-origin: 50% 110%; transform: rotate(var(--r)); box-shadow: 0 10px 30px -10px rgba(0,0,0,.6); transition: transform .35s ease; }
.sc1:hover > div, .sc1:focus-visible > div { transform: rotate(calc(var(--r) * 2.6)); }
.sc1:focus-visible { outline: 2px solid #fff; outline-offset: 10px; border-radius: 16px; }
@media (prefers-reduced-motion: reduce) { .sc1 > div { transition: none; } }

02. Swipe Deck with Next Button

StackJavaScript

A deck of tip cards where the top card slides away and moves to the back when you press Next. The deck order is real DOM order, so screen readers read the card on top first, and a live region announces which tip is showing.

View code
<div class="sc2"><div class="deck"><article>Tip 1: Name buttons after what they do.</article><article>Tip 2: Keep line length near 65 characters.</article><article>Tip 3: Test with the keyboard first.</article></div><div class="row"><p aria-live="polite">Tip 1 of 3</p><button type="button">Next tip</button></div></div>
.sc2 { width: 260px; max-width: 100%; font-family: Inter, system-ui, sans-serif; }
.sc2 .deck { position: relative; height: 150px; isolation: isolate; }
.sc2 article { position: absolute; inset: 0; box-sizing: border-box; padding: 20px; border: 1px solid #e4e7ec; border-radius: 16px; background: #fff; color: #101828; font-weight: 700; line-height: 1.4; box-shadow: 0 6px 16px -10px rgba(16,24,40,.4); transition: transform .35s ease, opacity .35s ease; }
.sc2 article:nth-child(2) { transform: translateY(10px) scale(.95); z-index: -1; }
.sc2 article:nth-child(3) { transform: translateY(20px) scale(.9); z-index: -2; }
.sc2 article.out { transform: translateX(120%) rotate(12deg); opacity: 0; }
.sc2 .row { display: flex; align-items: center; justify-content: space-between; margin-top: 34px; }
.sc2 p { margin: 0; color: #475467; font-size: 13px; }
.sc2 button { padding: 9px 14px; border: 0; border-radius: 8px; background: #101828; color: #fff; font: 700 13px Inter, system-ui, sans-serif; cursor: pointer; }
.sc2 button:focus-visible { outline: 3px solid #84adff; outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) { .sc2 article { transition: none; } }
var d = document.querySelector('.sc2 .deck'), p = document.querySelector('.sc2 p'), n = 1, busy = false;
document.querySelector('.sc2 button').onclick = function () {
  if (busy) return; busy = true;
  var top = d.firstElementChild; top.classList.add('out');
  setTimeout(function () { top.classList.remove('out'); d.appendChild(top); n = n % 3 + 1; p.textContent = 'Tip ' + n + ' of 3'; busy = false; }, 350);
};

03. Cards That Stack While Scrolling

StackSticky

Inside a scrolling panel, each card is position: sticky with a slightly larger top offset than the last, so cards pile up on each other as you scroll and leave a sliver of each one visible. It is pure CSS and works with the scroll wheel, keyboard and touch.

View code
<div class="sc3" tabindex="0" role="region" aria-label="Project stages, scrollable"><article style="--i:0;--c:#1e3a8a"><h3>Discover</h3><p>Workshops and research.</p></article><article style="--i:1;--c:#5b21b6"><h3>Design</h3><p>Wireframes and visual design.</p></article><article style="--i:2;--c:#9d174d"><h3>Build</h3><p>Front end and integrations.</p></article><article style="--i:3;--c:#065f46"><h3>Launch</h3><p>Testing, training and go-live.</p></article></div>
.sc3 { width: 270px; max-width: 100%; height: 300px; overflow-y: auto; padding: 8px; box-sizing: border-box; border-radius: 18px; background: #0b0f1a; font-family: Inter, system-ui, sans-serif; }
.sc3:focus-visible { outline: 2px solid #818cf8; outline-offset: 2px; }
.sc3 article { position: sticky; top: calc(var(--i) * 14px); height: 170px; box-sizing: border-box; margin-bottom: 40px; padding: 18px; border-radius: 14px; background: var(--c); color: #fff; box-shadow: 0 -6px 18px -8px rgba(0,0,0,.6); }
.sc3 article:last-child { margin-bottom: 0; }
.sc3 h3 { margin: 0 0 6px; } .sc3 p { margin: 0; opacity: .9; font-size: 14px; }

04. Stack That Spreads into a Row

StackLayout

Four small cards are piled up with negative margins and spread into a neat row when the group is hovered or focused, like a tidy hand of cards. The cards are list items, so the list is announced with its count.

View code
<ul class="sc4" tabindex="0" aria-label="Team members: AL, JP, SK, MR"><li style="--c:#fecaca">AL</li><li style="--c:#fde68a">JP</li><li style="--c:#bbf7d0">SK</li><li style="--c:#bfdbfe">MR</li></ul>
.sc4 { display: flex; margin: 0; padding: 8px; list-style: none; border-radius: 16px; font: 800 18px Inter, system-ui, sans-serif; }
.sc4 li { display: grid; place-items: center; width: 64px; height: 84px; border: 2px solid #fff; border-radius: 14px; background: var(--c); color: #1f2937; box-shadow: 0 6px 14px -8px rgba(0,0,0,.45); transition: margin .3s ease, transform .3s ease; }
.sc4 li + li { margin-left: -44px; }
.sc4 li:nth-child(odd) { transform: rotate(-4deg); } .sc4 li:nth-child(even) { transform: rotate(3deg); }
.sc4:hover li + li, .sc4:focus-visible li + li { margin-left: 8px; }
.sc4:hover li, .sc4:focus-visible li { transform: none; }
.sc4:focus-visible { outline: 3px solid #528bff; outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) { .sc4 li { transition: none; } }

05. Stacked Notifications

StackNotifications

Notification cards collapse into a neat stack with only the edges of older ones peeking out, and expand into a list when the button above them is pressed. The button has aria-expanded, so the collapsed and expanded states are announced.

View code
<div class="sc5"><button type="button" aria-expanded="false" aria-controls="sc5l">3 notifications</button><ul id="sc5l"><li><b>Order shipped</b>Your parcel is on its way.</li><li><b>New review</b>“Lovely mug!” 5 stars.</li><li><b>Payout sent</b>$420 to your bank.</li></ul></div>
.sc5 { width: 270px; max-width: 100%; font-family: Inter, system-ui, sans-serif; }
.sc5 button { margin-bottom: 10px; padding: 6px 10px; border: 1px solid #36406a; border-radius: 999px; background: #151b2e; color: #c7d2fe; font: 600 13px Inter, system-ui, sans-serif; cursor: pointer; }
.sc5 button:focus-visible { outline: 2px solid #a5b4fc; outline-offset: 2px; }
.sc5 ul { display: grid; margin: 0; padding: 0; list-style: none; isolation: isolate; }
.sc5 li { grid-area: 1 / 1; display: grid; gap: 2px; padding: 12px 14px; border: 1px solid #2c3554; border-radius: 14px; background: #1a2135; color: #9aa6c7; font-size: 13px; transition: transform .3s ease, opacity .3s ease; }
.sc5 li b { color: #fff; font-size: 14px; }
.sc5 li:nth-child(2) { transform: translateY(10px) scale(.94); opacity: .7; z-index: -1; }
.sc5 li:nth-child(3) { transform: translateY(20px) scale(.88); opacity: .45; z-index: -2; }
.sc5.open ul { gap: 8px; } .sc5.open li { grid-area: auto; transform: none; opacity: 1; }
@media (prefers-reduced-motion: reduce) { .sc5 li { transition: none; } }
var w = document.querySelector('.sc5'), b = w.querySelector('button');
b.onclick = function () { var o = w.classList.toggle('open'); b.setAttribute('aria-expanded', o); };

06. Polaroid Photo Pile

StackGallery

Three instant-photo frames lie on a pile at different angles, and the one under the pointer straightens and rises to the top with a higher z-index. The photos are gradient stand-ins with captions in figcaption elements.

View code
<div class="sc6"><figure style="--r:-9deg;--x:-40px;--g:linear-gradient(135deg,#fbbf24,#ef4444)"><div></div><figcaption>Lisbon</figcaption></figure><figure style="--r:6deg;--x:40px;--g:linear-gradient(135deg,#34d399,#0ea5e9)"><div></div><figcaption>Azores</figcaption></figure><figure style="--r:-2deg;--x:0px;--g:linear-gradient(135deg,#a78bfa,#ec4899)"><div></div><figcaption>Porto</figcaption></figure></div>
.sc6 { position: relative; display: grid; place-items: center; width: 280px; max-width: 100%; height: 260px; }
.sc6 figure { grid-area: 1 / 1; width: 140px; margin: 0; padding: 10px 10px 8px; background: #fff; box-shadow: 0 10px 20px -10px rgba(0,0,0,.45); transform: translateX(var(--x)) rotate(var(--r)); transition: transform .3s ease; font: 600 13px "JetBrains Mono", ui-monospace, monospace; color: #344054; text-align: center; }
.sc6 figure div { aspect-ratio: 1; background: var(--g); }
.sc6 figcaption { margin-top: 6px; }
.sc6 figure:hover { z-index: 2; transform: translateX(var(--x)) rotate(0) scale(1.06); }
@media (prefers-reduced-motion: reduce) { .sc6 figure { transition: none; } }

07. Layered Pricing Highlight

StackPricing

A single pricing card with two offset layers behind it, a quiet way to make the recommended plan feel weightier than its neighbours. The layers are two pseudo-elements, so the markup is just the card.

View code
<article class="sc7"><p class="tag">Most popular</p><h3>Growth</h3><p class="pr"><b>$29</b> / month</p><p class="d">For shops doing their first thousand orders.</p></article>
.sc7 { position: relative; isolation: isolate; width: 230px; max-width: 100%; box-sizing: border-box; padding: 22px; border-radius: 18px; background: #1e293b; border: 1px solid #334155; color: #cbd5e1; font-family: Inter, system-ui, sans-serif; }
.sc7::before, .sc7::after { content: ""; position: absolute; left: 12px; right: 12px; border-radius: 18px; z-index: -1; }
.sc7::before { bottom: -10px; height: 40px; background: #334155; }
.sc7::after { bottom: -20px; left: 24px; right: 24px; height: 40px; background: #263244; z-index: -2; }
.sc7 .tag { display: inline-block; margin: 0 0 10px; padding: 3px 9px; border-radius: 999px; background: #22d3ee; color: #083344; font-size: 12px; font-weight: 800; }
.sc7 h3 { margin: 0; color: #fff; font-size: 20px; }
.sc7 .pr { margin: 6px 0; } .sc7 .pr b { color: #fff; font-size: 30px; }
.sc7 .d { margin: 0; font-size: 14px; }

About these CSS Stacked Cards

Stacking in one grid cell

Putting every card in the same grid area with grid-area: 1 / 1 stacks them without absolute positioning, so the container still sizes itself to the cards. Rotate or offset each one with a custom property.

Order matters for screen readers

Assistive technology reads cards in DOM order, not visual order. Keep the card that looks on top first in the markup, and when a deck changes, move the element in the DOM rather than only changing z-index.

Sticky stacking on scroll

Cards with position: sticky and increasing top values pile on top of each other as the page scrolls past them. It is pure CSS and works with every input method.

Keep the cue when it moves

Fanning or spreading on hover should also happen on :focus-visible, and a stack that hides content needs a button with aria-expanded so everyone can open it.

Frequently asked questions

Why use isolation: isolate?

Cards with negative z-index would otherwise slip behind the page background. isolation creates a new stacking context so they stay inside the component.

Do the stacks work on touch screens?

The deck and notifications use buttons, and the scroll stack uses touch scrolling. Hover-only fans simply stay closed.

Can I put real images in the photo pile?

Yes. Replace the gradient div with an img and keep its alt text.