Card Stack
Overview
Click or swipe the top card and it flies off, then returns to the bottom of the stack.
Plain HTML, CSS and a little vanilla JS, with no framework dependency. Copy the prompt into Claude Code, Cursor, Trae or Codex and the model adds Card Stack to your project in your colors.
Where to use it
Sections that present information in groups: features, case studies and work, team members, article lists.
Install
With Builder, install it with the 8pt CLI or call it directly from an editor connected to 8pt MCP.
$ npm i -g @8pt-dev/cli && 8pt add card-stack
mcp › Use 8pt MCP to call get_component("card-stack")
Code
Source for Card Stack. Motion uses only transform and opacity and respects the Reduce motion setting.
<div class="x-cs"> <div class="x-cs__card" style="--c:#4D74FF"><small>Spacing</small><b>8 · 16 · 24 · 32</b><span class="x-cs__bars"><i style="width:8px"></i><i style="width:16px"></i><i style="width:24px"></i><i style="width:32px"></i></span></div> <div class="x-cs__card" style="--c:#00a3ff"><small>Radius</small><b>4 · 8 · 12 · 16</b><span class="x-cs__rad"><i></i><i></i><i></i><i></i></span></div> <div class="x-cs__card" style="--c:#ff4fd8"><small>Type</small><b>12 · 14 · 16 · 24</b><span class="x-cs__type">Aa Aa <em>Aa</em></span></div> <div class="x-cs__card" style="--c:#9bd400"><small>Motion</small><b>160 · 240 · 400 ms</b><span class="x-cs__bars"><i style="width:16px"></i><i style="width:24px"></i><i style="width:40px"></i></span></div> </div>
.x-cs{position:relative;width:220px;height:150px;touch-action:pan-y}
.x-cs__card{position:absolute;inset:0;display:flex;flex-direction:column;gap:6px;padding:18px;border-radius:18px;background:var(--d-card,#101016);border:1px solid var(--d-line,rgba(255,255,255,.12));box-shadow:0 20px 40px -24px rgba(0,0,0,.7);color:var(--d-fg,#fff);cursor:grab;user-select:none;transition:transform .5s cubic-bezier(.2,.9,.2,1),opacity .4s;will-change:transform}
.x-cs__card::before{content:"";position:absolute;inset:0 0 auto;height:3px;border-radius:18px 18px 0 0;background:var(--c)}
.x-cs__card small{font:600 11px/1 ui-monospace,monospace;color:var(--d-muted,#a0a0ab);letter-spacing:.06em;text-transform:uppercase}
.x-cs__card b{font:700 19px/1.2 var(--d-font,system-ui,sans-serif);letter-spacing:-.01em}
.x-cs__bars,.x-cs__rad{display:flex;gap:6px;align-items:flex-end;margin-top:auto}
.x-cs__bars i{height:22px;border-radius:3px;background:var(--c);opacity:.8}
.x-cs__rad i{width:26px;height:26px;background:var(--c);opacity:.8}
.x-cs__rad i:nth-child(1){border-radius:4px}.x-cs__rad i:nth-child(2){border-radius:8px}.x-cs__rad i:nth-child(3){border-radius:12px}.x-cs__rad i:nth-child(4){border-radius:16px}
.x-cs__type{margin-top:auto;font:600 14px/1 var(--d-font,system-ui,sans-serif);color:var(--c)}
.x-cs__type em{font-size:24px;font-style:normal}const init = (root) => {
const box = root.querySelector('.x-cs'); let cards = [...box.children];
const layout = () => cards.forEach((c, i) => { c.style.zIndex = cards.length - i; c.style.opacity = i > 2 ? 0 : 1; c.style.transform = `translateY(${i * -12}px) scale(${1 - i * 0.06})`; });
const fling = (dir) => {
const top = cards[0]; top.style.transform = `translateX(${dir * 260}px) rotate(${dir * 18}deg)`; top.style.opacity = 0;
setTimeout(() => { cards.push(cards.shift()); top.style.transition = 'none'; layout(); top.getBoundingClientRect(); top.style.transition = ''; }, 320);
};
let sx = null, dx = 0;
box.addEventListener('pointerdown', e => { if (e.target.closest('.x-cs__card') !== cards[0]) return; sx = e.clientX; dx = 0; cards[0].style.transition = 'none'; cards[0].setPointerCapture(e.pointerId); });
box.addEventListener('pointermove', e => { if (sx === null) return; dx = e.clientX - sx; cards[0].style.transform = `translateX(${dx}px) rotate(${dx / 14}deg)`; });
box.addEventListener('pointerup', () => { if (sx === null) return; cards[0].style.transition = ''; sx = null; Math.abs(dx) > 70 ? fling(Math.sign(dx)) : Math.abs(dx) < 4 ? fling(1) : layout(); });
layout();
…Subscribe to Builder to see the full source.See pricing