36 Tailwind Stacked Cards
09 / 36

All 36 in 36 Tailwind Stacked Cards

  1. 01Tailwind Scroll-Driven Scaling Card Stack
  2. 02Tailwind Swipe Card Deck Drag to Dismiss
  3. 03Tailwind Glassmorphism Testimonial Card Stack
  4. 04Tailwind Stacked Cards Expand Accordion on Click
  5. 05Tailwind Fan Out Photo Stack on Hover
  6. 06Tailwind 3D Perspective Tilt Card Stack
  7. 07Tailwind Multi Step Form Card Stack
  8. 08Tailwind Tabbed Card Stack Bring to Front
  9. 09Tailwind Click to Front Stacked Cards Cycle
  10. 10Tailwind Classic Card Deck Stack Shadow
  11. 11Tailwind Fan Spread Hand of Cards
  12. 12Tailwind Polaroid Photo Stack Scattered Rotate
  13. 13Tailwind Staircase Offset Stacked Cards Cascade
  14. 14Tailwind Cards Spread Out Horizontally on Hover
  15. 15Tailwind Stacked Cards Lift on Hover Sibling Push
  16. 16Tailwind Accordion Stacked Cards Slide Out Menu
  17. 17Tailwind Shuffle Cards Animation Loop Deck
  18. 18Tailwind File Folder Tab Stacked Cards
  19. 19Tailwind Scattered Photos Pinch to Centre on Hover
  20. 20Tailwind Mobile Swipe Card Stack Translate3d
  21. 21Tailwind Single Accordion Card Nested Layers
  22. 22Tailwind Isometric Tower Stacked Cards 3D Column
  23. 23Tailwind 3D Flip Card Stack Reveal Back
  24. 24Tailwind Card Stack Tilt on Hover Quadrant
  25. 25Tailwind Notification Stack Pile Dismiss
  26. 26Tailwind Receipt Roll Unroll Stacked Items
  27. 27Tailwind Scratch Card Reveal Canvas Mask
  28. 28Tailwind Stacked Cards Hover Reveal Spread
  29. 29Tailwind Scroll Activated Sticky Stacking Cards
  30. 30Tailwind Stacked Testimonial Slider Radio
  31. 31Tailwind Isometric Overlapping Stacked Cards
  32. 32Tailwind Bento Grid Stacked Cards Hover Fan
  33. 33Tailwind Perspective Card Deck Depth Tunnel
  34. 34Tailwind Peel Back Corner Card Reveal
  35. 35Tailwind Timeline Stacked Milestone Cards
  36. 36Tailwind Spiral Tower Rotating Stacked Cards
Tailwind v4MIT licensed

Tailwind Click to Front Stacked Cards Cycle

A queue you browse by clicking the front card: it flings sideways, drops to the bottom of the pile and glides back in at the rear. Depth is a number, not DOM order — every card reads its own --depth through [transform:translateY(calc(var(--depth)*16px))_scale(calc(1-var(--depth)*0.05))] and [z-index:calc(10-var(--depth))], so the script only ever repaints one integer per card.

Live Demo
Plain CSSTailwind v4
Try it

Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time

Untitled UI React · Affiliate link

Get Untitled UI Pro

The code

<section class="stk-09 w-full min-h-svh flex flex-col items-center justify-center gap-7 px-5 py-12 font-stk09-sans bg-stk09-bg" aria-label="Click-to-front card deck">
  <div class="relative w-[min(320px,86vw)] h-55" id="stk-09-deck">
    <button class="absolute inset-0 flex flex-col gap-2 border-0 cursor-pointer rounded-[20px] bg-stk09-c1 p-6 text-left text-white shadow-[0_24px_46px_-26px_rgba(0,0,0,0.8)] [transform:translateY(calc(var(--depth,0)*16px))_scale(calc(1-var(--depth,0)*0.05))] [z-index:calc(10-var(--depth,0))] transition-[transform,opacity] duration-500 ease-stk09 data-[sending=true]:opacity-0 data-[sending=true]:duration-450 data-[sending=true]:ease-in data-[sending=true]:[transform:translateX(130%)_rotate(12deg)] focus-visible:outline-3 focus-visible:outline-white focus-visible:outline-offset-3 motion-reduce:duration-200" type="button">
      <span class="font-stk09-mono text-xs font-bold tracking-[0.08em] opacity-85">Ticket #4821</span>
      <b class="text-xl font-extrabold tracking-[-0.02em]">Payment retry loop</b>
      <i class="mt-auto font-stk09-mono text-xs not-italic opacity-85">High &middot; Billing</i>
    </button>
    <button class="absolute inset-0 flex flex-col gap-2 border-0 cursor-pointer rounded-[20px] bg-stk09-c2 p-6 text-left text-white shadow-[0_24px_46px_-26px_rgba(0,0,0,0.8)] [transform:translateY(calc(var(--depth,0)*16px))_scale(calc(1-var(--depth,0)*0.05))] [z-index:calc(10-var(--depth,0))] transition-[transform,opacity] duration-500 ease-stk09 data-[sending=true]:opacity-0 data-[sending=true]:duration-450 data-[sending=true]:ease-in data-[sending=true]:[transform:translateX(130%)_rotate(12deg)] focus-visible:outline-3 focus-visible:outline-white focus-visible:outline-offset-3 motion-reduce:duration-200" type="button">
      <span class="font-stk09-mono text-xs font-bold tracking-[0.08em] opacity-85">Ticket #4822</span>
      <b class="text-xl font-extrabold tracking-[-0.02em]">Slow search on mobile</b>
      <i class="mt-auto font-stk09-mono text-xs not-italic opacity-85">Medium &middot; Web</i>
    </button>
    <button class="absolute inset-0 flex flex-col gap-2 border-0 cursor-pointer rounded-[20px] bg-stk09-c3 p-6 text-left text-white shadow-[0_24px_46px_-26px_rgba(0,0,0,0.8)] [transform:translateY(calc(var(--depth,0)*16px))_scale(calc(1-var(--depth,0)*0.05))] [z-index:calc(10-var(--depth,0))] transition-[transform,opacity] duration-500 ease-stk09 data-[sending=true]:opacity-0 data-[sending=true]:duration-450 data-[sending=true]:ease-in data-[sending=true]:[transform:translateX(130%)_rotate(12deg)] focus-visible:outline-3 focus-visible:outline-white focus-visible:outline-offset-3 motion-reduce:duration-200" type="button">
      <span class="font-stk09-mono text-xs font-bold tracking-[0.08em] opacity-85">Ticket #4823</span>
      <b class="text-xl font-extrabold tracking-[-0.02em]">Export to CSV missing</b>
      <i class="mt-auto font-stk09-mono text-xs not-italic opacity-85">Low &middot; Reports</i>
    </button>
    <button class="absolute inset-0 flex flex-col gap-2 border-0 cursor-pointer rounded-[20px] bg-stk09-c4 p-6 text-left text-white shadow-[0_24px_46px_-26px_rgba(0,0,0,0.8)] [transform:translateY(calc(var(--depth,0)*16px))_scale(calc(1-var(--depth,0)*0.05))] [z-index:calc(10-var(--depth,0))] transition-[transform,opacity] duration-500 ease-stk09 data-[sending=true]:opacity-0 data-[sending=true]:duration-450 data-[sending=true]:ease-in data-[sending=true]:[transform:translateX(130%)_rotate(12deg)] focus-visible:outline-3 focus-visible:outline-white focus-visible:outline-offset-3 motion-reduce:duration-200" type="button">
      <span class="font-stk09-mono text-xs font-bold tracking-[0.08em] opacity-85">Ticket #4824</span>
      <b class="text-xl font-extrabold tracking-[-0.02em]">SSO redirect fails</b>
      <i class="mt-auto font-stk09-mono text-xs not-italic opacity-85">High &middot; Auth</i>
    </button>
  </div>
  <div class="flex flex-col items-center gap-2">
    <button class="rounded-xl border border-stk09-line bg-stk09-btn px-5.5 py-2.75 text-sm font-bold text-white cursor-pointer transition-colors duration-200 hover:bg-stk09-btnh focus-visible:outline-3 focus-visible:outline-stk09-ring focus-visible:outline-offset-2" id="stk-09-next" type="button">Next ticket &#8635;</button>
    <p class="sr-only" id="stk-09-live" aria-live="polite"></p>
  </div>
</section>
@import "tailwindcss";

@theme {
  --color-stk09-bg: #101219;
  --color-stk09-bg: oklch(0.19 0.012 265);
  --color-stk09-btn: #232838;
  --color-stk09-btn: oklch(0.28 0.018 265);
  --color-stk09-btnh: #2e3446;
  --color-stk09-btnh: oklch(0.33 0.02 265);
  --color-stk09-line: #333a4e;
  --color-stk09-line: oklch(0.36 0.02 265);
  --color-stk09-ring: #5aa2e0;
  --color-stk09-ring: oklch(0.66 0.15 250);
  --color-stk09-c1: #d2604c;
  --color-stk09-c1: oklch(0.63 0.17 20);
  --color-stk09-c2: #45ab7c;
  --color-stk09-c2: oklch(0.66 0.15 150);
  --color-stk09-c3: #4d8ade;
  --color-stk09-c3: oklch(0.64 0.16 250);
  --color-stk09-c4: #9b6ed6;
  --color-stk09-c4: oklch(0.64 0.16 300);
  --font-stk09-sans: 'Archivo', system-ui, sans-serif;
  --font-stk09-mono: 'JetBrains Mono', ui-monospace, monospace;
  --ease-stk09: cubic-bezier(0.3, 0.9, 0.3, 1);
}
(() => {
  const root = document.querySelector('.stk-09');
  if (!root) return;
  const deck = root.querySelector('#stk-09-deck');
  const live = root.querySelector('#stk-09-live');

  /* The controller writes ONE integer per card. Translate, scale and z-index
     are all derived from it by arbitrary calc() utilities in the markup. */
  const paint = () => [...deck.children].forEach((c, i) => c.style.setProperty('--depth', i));

  function cycle() {
    const front = deck.firstElementChild;
    if (!front) return;
    front.dataset.sending = 'true';
    front.addEventListener('transitionend', () => {
      delete front.dataset.sending;
      deck.appendChild(front);
      paint();
      const title = deck.firstElementChild.querySelector('b');
      if (title) live.textContent = 'Now showing ' + title.textContent;
    }, { once: true });
  }

  deck.addEventListener('click', (e) => {
    if (e.target.closest('button') === deck.firstElementChild) cycle();
  });
  root.querySelector('#stk-09-next').addEventListener('click', cycle);
  paint();
})();

Setup — do this once per project

Skip this if your project already runs Tailwind v4.

1Load Tailwind
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>

Compiles in the browser — perfect for a CodePen or a quick test, not for production.

2Add the stylesheetRequiredthe @theme block is what makes the custom utilities exist
@import "tailwindcss";

@theme {
  --color-stk09-bg: #101219;
  --color-stk09-bg: oklch(0.19 0.012 265);
  --color-stk09-btn: #232838;
  --color-stk09-btn: oklch(0.28 0.018 265);
  --color-stk09-btnh: #2e3446;
  --color-stk09-btnh: oklch(0.33 0.02 265);
  --color-stk09-line: #333a4e;
  --color-stk09-line: oklch(0.36 0.02 265);
  --color-stk09-ring: #5aa2e0;
  --color-stk09-ring: oklch(0.66 0.15 250);
  --color-stk09-c1: #d2604c;
  --color-stk09-c1: oklch(0.63 0.17 20);
  --color-stk09-c2: #45ab7c;
  --color-stk09-c2: oklch(0.66 0.15 150);
  --color-stk09-c3: #4d8ade;
  --color-stk09-c3: oklch(0.64 0.16 250);
  --color-stk09-c4: #9b6ed6;
  --color-stk09-c4: oklch(0.64 0.16 300);
  --font-stk09-sans: 'Archivo', system-ui, sans-serif;
  --font-stk09-mono: 'JetBrains Mono', ui-monospace, monospace;
  --ease-stk09: cubic-bezier(0.3, 0.9, 0.3, 1);
}
3Fonts used by this demooptional — skip if you have your own type stack
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Archivo:wdth,wght@75..125,400;75..125,700;75..125,800&family=JetBrains+Mono:wght@400;500&display=swap">

What changed in the port

Which parts became utilities, which stayed CSS, and why.

  • The webfonts load as <link> tags in setup, not as an @import inside the stylesheet. Tailwind emits user @import rules after its own @layer blocks, and a late @import is invalid CSS — the browser drops it and the demo silently falls back to system fonts.
  • One number does three jobs: --depth feeds the translate, the scale and [z-index:calc(10-var(--depth,0))], so a reshuffle is a single integer write per card.
  • z-index has no calc-able utility and never will — this is exactly the case arbitrary properties are for.
  • The .sending class becomes data-[sending=true]:, which also carries its own duration-450 ease-in: the exit is faster and sharper than the settle, described on the element rather than in a second rule.
  • The --depth fallback is written into the utility (var(--depth,0)), so the deck renders correctly before the script runs — no flash of stacked-at-zero cards.
  • Cards are real <button>s, which removes the tabindex and the synthetic Enter/Space handling the CSS version needs.
  • The scoped sr-only helper becomes Tailwind's sr-only on the aria-live region — the one part of this demo a sighted user never sees and a screen-reader user depends on.
Paste this into ChatGPT, Claude, Cursor, or any coding assistant. The block below is pre-framed with everything the AI needs to integrate this demo into your project — markup, styles, scoping notes, and the source URL. Hit Copy and paste straight into your chat.
Here's a working CSS demo from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Tailwind Click to Front Stacked Cards Cycle
Source: https://codefronts.com/components/tailwind-stacked-cards/tailwind-click-to-front-stacked-cards-cycle/

A queue you browse by clicking the front card: it flings sideways, drops to the bottom of the pile and glides back in at the rear. Depth is a number, not DOM order &mdash; every card reads its own --depth through [transform:translateY(calc(var(--depth)*16px))_scale(calc(1-var(--depth)*0.05))] and [z-index:calc(10-var(--depth))], so the script only ever repaints one integer per card.
## HTML
```html
<section class="stk-09 w-full min-h-svh flex flex-col items-center justify-center gap-7 px-5 py-12 font-stk09-sans bg-stk09-bg" aria-label="Click-to-front card deck">
  <div class="relative w-[min(320px,86vw)] h-55" id="stk-09-deck">
    <button class="absolute inset-0 flex flex-col gap-2 border-0 cursor-pointer rounded-[20px] bg-stk09-c1 p-6 text-left text-white shadow-[0_24px_46px_-26px_rgba(0,0,0,0.8)] [transform:translateY(calc(var(--depth,0)*16px))_scale(calc(1-var(--depth,0)*0.05))] [z-index:calc(10-var(--depth,0))] transition-[transform,opacity] duration-500 ease-stk09 data-[sending=true]:opacity-0 data-[sending=true]:duration-450 data-[sending=true]:ease-in data-[sending=true]:[transform:translateX(130%)_rotate(12deg)] focus-visible:outline-3 focus-visible:outline-white focus-visible:outline-offset-3 motion-reduce:duration-200" type="button">
      <span class="font-stk09-mono text-xs font-bold tracking-[0.08em] opacity-85">Ticket #4821</span>
      <b class="text-xl font-extrabold tracking-[-0.02em]">Payment retry loop</b>
      <i class="mt-auto font-stk09-mono text-xs not-italic opacity-85">High &middot; Billing</i>
    </button>
    <button class="absolute inset-0 flex flex-col gap-2 border-0 cursor-pointer rounded-[20px] bg-stk09-c2 p-6 text-left text-white shadow-[0_24px_46px_-26px_rgba(0,0,0,0.8)] [transform:translateY(calc(var(--depth,0)*16px))_scale(calc(1-var(--depth,0)*0.05))] [z-index:calc(10-var(--depth,0))] transition-[transform,opacity] duration-500 ease-stk09 data-[sending=true]:opacity-0 data-[sending=true]:duration-450 data-[sending=true]:ease-in data-[sending=true]:[transform:translateX(130%)_rotate(12deg)] focus-visible:outline-3 focus-visible:outline-white focus-visible:outline-offset-3 motion-reduce:duration-200" type="button">
      <span class="font-stk09-mono text-xs font-bold tracking-[0.08em] opacity-85">Ticket #4822</span>
      <b class="text-xl font-extrabold tracking-[-0.02em]">Slow search on mobile</b>
      <i class="mt-auto font-stk09-mono text-xs not-italic opacity-85">Medium &middot; Web</i>
    </button>
    <button class="absolute inset-0 flex flex-col gap-2 border-0 cursor-pointer rounded-[20px] bg-stk09-c3 p-6 text-left text-white shadow-[0_24px_46px_-26px_rgba(0,0,0,0.8)] [transform:translateY(calc(var(--depth,0)*16px))_scale(calc(1-var(--depth,0)*0.05))] [z-index:calc(10-var(--depth,0))] transition-[transform,opacity] duration-500 ease-stk09 data-[sending=true]:opacity-0 data-[sending=true]:duration-450 data-[sending=true]:ease-in data-[sending=true]:[transform:translateX(130%)_rotate(12deg)] focus-visible:outline-3 focus-visible:outline-white focus-visible:outline-offset-3 motion-reduce:duration-200" type="button">
      <span class="font-stk09-mono text-xs font-bold tracking-[0.08em] opacity-85">Ticket #4823</span>
      <b class="text-xl font-extrabold tracking-[-0.02em]">Export to CSV missing</b>
      <i class="mt-auto font-stk09-mono text-xs not-italic opacity-85">Low &middot; Reports</i>
    </button>
    <button class="absolute inset-0 flex flex-col gap-2 border-0 cursor-pointer rounded-[20px] bg-stk09-c4 p-6 text-left text-white shadow-[0_24px_46px_-26px_rgba(0,0,0,0.8)] [transform:translateY(calc(var(--depth,0)*16px))_scale(calc(1-var(--depth,0)*0.05))] [z-index:calc(10-var(--depth,0))] transition-[transform,opacity] duration-500 ease-stk09 data-[sending=true]:opacity-0 data-[sending=true]:duration-450 data-[sending=true]:ease-in data-[sending=true]:[transform:translateX(130%)_rotate(12deg)] focus-visible:outline-3 focus-visible:outline-white focus-visible:outline-offset-3 motion-reduce:duration-200" type="button">
      <span class="font-stk09-mono text-xs font-bold tracking-[0.08em] opacity-85">Ticket #4824</span>
      <b class="text-xl font-extrabold tracking-[-0.02em]">SSO redirect fails</b>
      <i class="mt-auto font-stk09-mono text-xs not-italic opacity-85">High &middot; Auth</i>
    </button>
  </div>
  <div class="flex flex-col items-center gap-2">
    <button class="rounded-xl border border-stk09-line bg-stk09-btn px-5.5 py-2.75 text-sm font-bold text-white cursor-pointer transition-colors duration-200 hover:bg-stk09-btnh focus-visible:outline-3 focus-visible:outline-stk09-ring focus-visible:outline-offset-2" id="stk-09-next" type="button">Next ticket &#8635;</button>
    <p class="sr-only" id="stk-09-live" aria-live="polite"></p>
  </div>
</section>
```
## CSS
```css
@import "tailwindcss";

@theme {
  --color-stk09-bg: #101219;
  --color-stk09-bg: oklch(0.19 0.012 265);
  --color-stk09-btn: #232838;
  --color-stk09-btn: oklch(0.28 0.018 265);
  --color-stk09-btnh: #2e3446;
  --color-stk09-btnh: oklch(0.33 0.02 265);
  --color-stk09-line: #333a4e;
  --color-stk09-line: oklch(0.36 0.02 265);
  --color-stk09-ring: #5aa2e0;
  --color-stk09-ring: oklch(0.66 0.15 250);
  --color-stk09-c1: #d2604c;
  --color-stk09-c1: oklch(0.63 0.17 20);
  --color-stk09-c2: #45ab7c;
  --color-stk09-c2: oklch(0.66 0.15 150);
  --color-stk09-c3: #4d8ade;
  --color-stk09-c3: oklch(0.64 0.16 250);
  --color-stk09-c4: #9b6ed6;
  --color-stk09-c4: oklch(0.64 0.16 300);
  --font-stk09-sans: 'Archivo', system-ui, sans-serif;
  --font-stk09-mono: 'JetBrains Mono', ui-monospace, monospace;
  --ease-stk09: cubic-bezier(0.3, 0.9, 0.3, 1);
}
```

## JavaScript
```js
(() => {
  const root = document.querySelector('.stk-09');
  if (!root) return;
  const deck = root.querySelector('#stk-09-deck');
  const live = root.querySelector('#stk-09-live');

  /* The controller writes ONE integer per card. Translate, scale and z-index
     are all derived from it by arbitrary calc() utilities in the markup. */
  const paint = () => [...deck.children].forEach((c, i) => c.style.setProperty('--depth', i));

  function cycle() {
    const front = deck.firstElementChild;
    if (!front) return;
    front.dataset.sending = 'true';
    front.addEventListener('transitionend', () => {
      delete front.dataset.sending;
      deck.appendChild(front);
      paint();
      const title = deck.firstElementChild.querySelector('b');
      if (title) live.textContent = 'Now showing ' + title.textContent;
    }, { once: true });
  }

  deck.addEventListener('click', (e) => {
    if (e.target.closest('button') === deck.firstElementChild) cycle();
  });
  root.querySelector('#stk-09-next').addEventListener('click', cycle);
  paint();
})();
```

How this works

Depth is data. The controller sets --depth to each card's index and nothing else. Three arbitrary values turn that one number into the whole visual hierarchy:

[transform:translateY(calc(var(--depth)*16px))_scale(calc(1-var(--depth)*0.05))]
[z-index:calc(10-var(--depth))]

Because depth is not DOM order, re-appending the flung card to the end of the deck does not fight the CSS — the repaint simply gives every card its new number and the transition does the rest.

The exit is a variant, the reorder is a script. data-[sending=true] holds the fling: a translate, a rotate, an opacity and a shorter ease-in. The script waits for transitionend before moving the node, which is the difference between a glide and a snap.

Cards are buttons, and that settles accessibility. No tabindex, no synthetic key handling, native Enter and Space. The only thing left to add is the announcement: an aria-live region naming whichever ticket is now on top, because a purely visual reshuffle tells a screen-reader user nothing.

Make it yours

  • Change the fling by editing the data-[sending=true]:[transform:…] value — direction, distance and rotation are all in one place.
  • Tighten or deepen the pile via the 16px and 0.05 factors in the depth transform.
  • Add a Prev action by moving the last child to the front and repainting; the depth model needs no change.
  • Recolour a card by editing its own bg-stk09-* utility.
  • Add cards freely — the depth mapping is arithmetic, so the pile absorbs them without a new rule.

Gotchas — read before shipping

  • Drive depth from a custom property, not DOM order. Re-appending a node while CSS keys off :nth-child makes the whole pile jump.
  • Wait for transitionend before re-appending, or the card teleports instead of gliding.
  • z-index has no calc-able utility. [z-index:calc(10-var(--depth))] is the correct arbitrary property, not a workaround.
  • Clear data-sending before the reorder, or the returning card keeps the fling transform and never comes back.
  • Keep the Next button. Click-the-front-card alone is a discoverability problem as much as an accessibility one.
  • Announce the new front card. Without the aria-live region the reshuffle is silent to assistive technology.

Browser support

ChromeSafariFirefoxEdge
111+16.4+128+111+

Custom properties in calc(), transforms and transitionend are universal (Chrome 49+, Safari 10+, Firefox 52+). The floor is Tailwind v4's own generated sheet.

Techniques used in this demo

Theme tokensset in @theme, used by a utility

Arbitraryneeds bracket syntax

Search CodeFronts

Loading…