36 Tailwind Stacked Cards
17 / 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 Shuffle Cards Animation Loop Deck

A deck riffling itself, offset per card by a negative animation-delay so the loop never syncs. Hover and it stops dead and fans out to be read — group-hover:[animation-play-state:paused] plus a static fan transform. The reduced-motion path is the interesting one: it does not slow the loop down, it removes it and ships the fanned deck as the resting state.

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-17 w-full min-h-svh flex items-center justify-center px-5 py-15 font-stk17-sans bg-stk17-bg" aria-label="Shuffling card deck">
  <div class="has-focus-visible:outline-3 has-focus-visible:outline-stk17-ring has-focus-visible:outline-offset-[12px] has-focus-visible:rounded-[22px]">
    <div class="group relative w-37.5 h-50 outline-none" tabindex="0" role="img" aria-label="A deck of four suit cards, riffling. Hover or focus to pause and fan them out.">
      <div class="absolute inset-0 grid place-items-center rounded-2xl bg-stk17-c1 text-[52px] text-white shadow-[0_16px_32px_-16px_rgba(0,0,0,0.8)] animate-stk17-riffle [animation-delay:calc(var(--i)*-0.4s)] group-hover:[animation-play-state:paused] group-hover:[transform:translateX(calc((var(--i)-1.5)*54px))_rotate(calc((var(--i)-1.5)*6deg))] group-focus-visible:[animation-play-state:paused] group-focus-visible:[transform:translateX(calc((var(--i)-1.5)*54px))_rotate(calc((var(--i)-1.5)*6deg))] motion-reduce:animate-none motion-reduce:[transform:translateX(calc((var(--i)-1.5)*54px))_rotate(calc((var(--i)-1.5)*6deg))]" style="--i:0" aria-hidden="true">&diams;</div>
      <div class="absolute inset-0 grid place-items-center rounded-2xl bg-stk17-c2 text-[52px] text-white shadow-[0_16px_32px_-16px_rgba(0,0,0,0.8)] animate-stk17-riffle [animation-delay:calc(var(--i)*-0.4s)] group-hover:[animation-play-state:paused] group-hover:[transform:translateX(calc((var(--i)-1.5)*54px))_rotate(calc((var(--i)-1.5)*6deg))] group-focus-visible:[animation-play-state:paused] group-focus-visible:[transform:translateX(calc((var(--i)-1.5)*54px))_rotate(calc((var(--i)-1.5)*6deg))] motion-reduce:animate-none motion-reduce:[transform:translateX(calc((var(--i)-1.5)*54px))_rotate(calc((var(--i)-1.5)*6deg))]" style="--i:1" aria-hidden="true">&clubs;</div>
      <div class="absolute inset-0 grid place-items-center rounded-2xl bg-stk17-c3 text-[52px] text-white shadow-[0_16px_32px_-16px_rgba(0,0,0,0.8)] animate-stk17-riffle [animation-delay:calc(var(--i)*-0.4s)] group-hover:[animation-play-state:paused] group-hover:[transform:translateX(calc((var(--i)-1.5)*54px))_rotate(calc((var(--i)-1.5)*6deg))] group-focus-visible:[animation-play-state:paused] group-focus-visible:[transform:translateX(calc((var(--i)-1.5)*54px))_rotate(calc((var(--i)-1.5)*6deg))] motion-reduce:animate-none motion-reduce:[transform:translateX(calc((var(--i)-1.5)*54px))_rotate(calc((var(--i)-1.5)*6deg))]" style="--i:2" aria-hidden="true">&hearts;</div>
      <div class="absolute inset-0 grid place-items-center rounded-2xl bg-stk17-c4 text-[52px] text-white shadow-[0_16px_32px_-16px_rgba(0,0,0,0.8)] animate-stk17-riffle [animation-delay:calc(var(--i)*-0.4s)] group-hover:[animation-play-state:paused] group-hover:[transform:translateX(calc((var(--i)-1.5)*54px))_rotate(calc((var(--i)-1.5)*6deg))] group-focus-visible:[animation-play-state:paused] group-focus-visible:[transform:translateX(calc((var(--i)-1.5)*54px))_rotate(calc((var(--i)-1.5)*6deg))] motion-reduce:animate-none motion-reduce:[transform:translateX(calc((var(--i)-1.5)*54px))_rotate(calc((var(--i)-1.5)*6deg))]" style="--i:3" aria-hidden="true">&spades;</div>
    </div>
  </div>
</section>
@import "tailwindcss";

@theme {
  --color-stk17-bg: #131620;
  --color-stk17-bg: oklch(0.21 0.018 275);
  --color-stk17-ring: #45ab7c;
  --color-stk17-ring: oklch(0.66 0.15 150);
  --color-stk17-c1: #cf5f4b;
  --color-stk17-c1: oklch(0.62 0.16 20);
  --color-stk17-c2: #45ab7c;
  --color-stk17-c2: oklch(0.64 0.15 150);
  --color-stk17-c3: #4d8ade;
  --color-stk17-c3: oklch(0.62 0.16 250);
  --color-stk17-c4: #9b6ed6;
  --color-stk17-c4: oklch(0.62 0.16 300);
  --font-stk17-sans: 'Archivo', system-ui, sans-serif;
  --animate-stk17-riffle: stk-17-riffle 1.6s ease-in-out infinite;
}
/* The riffle path itself. z-index is not interpolated inside keyframes — it
   steps at each stop, which is exactly what a shuffle wants. */

@keyframes stk-17-riffle {
  0%,
    100% {
    transform: translateX(0) rotate(0);
    z-index: 2;
  }

  25% {
    transform: translateX(70px) rotate(10deg);
    z-index: 5;
  }

  50% {
    transform: translateX(0) rotate(0);
    z-index: 2;
  }

  75% {
    transform: translateX(-70px) rotate(-10deg);
    z-index: 1;
  }
}

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-stk17-bg: #131620;
  --color-stk17-bg: oklch(0.21 0.018 275);
  --color-stk17-ring: #45ab7c;
  --color-stk17-ring: oklch(0.66 0.15 150);
  --color-stk17-c1: #cf5f4b;
  --color-stk17-c1: oklch(0.62 0.16 20);
  --color-stk17-c2: #45ab7c;
  --color-stk17-c2: oklch(0.64 0.15 150);
  --color-stk17-c3: #4d8ade;
  --color-stk17-c3: oklch(0.62 0.16 250);
  --color-stk17-c4: #9b6ed6;
  --color-stk17-c4: oklch(0.62 0.16 300);
  --font-stk17-sans: 'Archivo', system-ui, sans-serif;
  --animate-stk17-riffle: stk-17-riffle 1.6s ease-in-out infinite;
}
/* The riffle path itself. z-index is not interpolated inside keyframes — it
   steps at each stop, which is exactly what a shuffle wants. */

@keyframes stk-17-riffle {
  0%,
    100% {
    transform: translateX(0) rotate(0);
    z-index: 2;
  }

  25% {
    transform: translateX(70px) rotate(10deg);
    z-index: 5;
  }

  50% {
    transform: translateX(0) rotate(0);
    z-index: 2;
  }

  75% {
    transform: translateX(-70px) rotate(-10deg);
    z-index: 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&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.
  • The loop is a --animate-* token, so duration, easing and infinite all arrive with one animate-stk17-riffle class.
  • [animation-delay:calc(var(--i)*-0.4s)] replaces four :nth-child rules, and the negative sign is the mechanism: it starts each card mid-cycle rather than staggering the starts.
  • animation-play-state has no utility, so group-hover:[animation-play-state:paused] is the idiomatic form — and pausing rather than removing is what lets the fan transform take over cleanly.
  • The fan is a centred signed-index expression, calc((var(--i)-1.5)*54px), so one class fans four cards symmetrically.
  • The reduced-motion path ships the fanned, readable state as the default rather than a slower riffle — the correct accommodation for purely decorative looping motion.
  • The deck carries role=&quot;img&quot; with a description, and the individual suit cards are aria-hidden: what matters to a screen reader is what the thing is, not four unlabelled glyphs.
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 Shuffle Cards Animation Loop Deck
Source: https://codefronts.com/components/tailwind-stacked-cards/tailwind-shuffle-cards-animation-loop-deck/

A deck riffling itself, offset per card by a negative animation-delay so the loop never syncs. Hover and it stops dead and fans out to be read &mdash; group-hover:[animation-play-state:paused] plus a static fan transform. The reduced-motion path is the interesting one: it does not slow the loop down, it removes it and ships the fanned deck as the resting state.
## HTML
```html
<section class="stk-17 w-full min-h-svh flex items-center justify-center px-5 py-15 font-stk17-sans bg-stk17-bg" aria-label="Shuffling card deck">
  <div class="has-focus-visible:outline-3 has-focus-visible:outline-stk17-ring has-focus-visible:outline-offset-[12px] has-focus-visible:rounded-[22px]">
    <div class="group relative w-37.5 h-50 outline-none" tabindex="0" role="img" aria-label="A deck of four suit cards, riffling. Hover or focus to pause and fan them out.">
      <div class="absolute inset-0 grid place-items-center rounded-2xl bg-stk17-c1 text-[52px] text-white shadow-[0_16px_32px_-16px_rgba(0,0,0,0.8)] animate-stk17-riffle [animation-delay:calc(var(--i)*-0.4s)] group-hover:[animation-play-state:paused] group-hover:[transform:translateX(calc((var(--i)-1.5)*54px))_rotate(calc((var(--i)-1.5)*6deg))] group-focus-visible:[animation-play-state:paused] group-focus-visible:[transform:translateX(calc((var(--i)-1.5)*54px))_rotate(calc((var(--i)-1.5)*6deg))] motion-reduce:animate-none motion-reduce:[transform:translateX(calc((var(--i)-1.5)*54px))_rotate(calc((var(--i)-1.5)*6deg))]" style="--i:0" aria-hidden="true">&diams;</div>
      <div class="absolute inset-0 grid place-items-center rounded-2xl bg-stk17-c2 text-[52px] text-white shadow-[0_16px_32px_-16px_rgba(0,0,0,0.8)] animate-stk17-riffle [animation-delay:calc(var(--i)*-0.4s)] group-hover:[animation-play-state:paused] group-hover:[transform:translateX(calc((var(--i)-1.5)*54px))_rotate(calc((var(--i)-1.5)*6deg))] group-focus-visible:[animation-play-state:paused] group-focus-visible:[transform:translateX(calc((var(--i)-1.5)*54px))_rotate(calc((var(--i)-1.5)*6deg))] motion-reduce:animate-none motion-reduce:[transform:translateX(calc((var(--i)-1.5)*54px))_rotate(calc((var(--i)-1.5)*6deg))]" style="--i:1" aria-hidden="true">&clubs;</div>
      <div class="absolute inset-0 grid place-items-center rounded-2xl bg-stk17-c3 text-[52px] text-white shadow-[0_16px_32px_-16px_rgba(0,0,0,0.8)] animate-stk17-riffle [animation-delay:calc(var(--i)*-0.4s)] group-hover:[animation-play-state:paused] group-hover:[transform:translateX(calc((var(--i)-1.5)*54px))_rotate(calc((var(--i)-1.5)*6deg))] group-focus-visible:[animation-play-state:paused] group-focus-visible:[transform:translateX(calc((var(--i)-1.5)*54px))_rotate(calc((var(--i)-1.5)*6deg))] motion-reduce:animate-none motion-reduce:[transform:translateX(calc((var(--i)-1.5)*54px))_rotate(calc((var(--i)-1.5)*6deg))]" style="--i:2" aria-hidden="true">&hearts;</div>
      <div class="absolute inset-0 grid place-items-center rounded-2xl bg-stk17-c4 text-[52px] text-white shadow-[0_16px_32px_-16px_rgba(0,0,0,0.8)] animate-stk17-riffle [animation-delay:calc(var(--i)*-0.4s)] group-hover:[animation-play-state:paused] group-hover:[transform:translateX(calc((var(--i)-1.5)*54px))_rotate(calc((var(--i)-1.5)*6deg))] group-focus-visible:[animation-play-state:paused] group-focus-visible:[transform:translateX(calc((var(--i)-1.5)*54px))_rotate(calc((var(--i)-1.5)*6deg))] motion-reduce:animate-none motion-reduce:[transform:translateX(calc((var(--i)-1.5)*54px))_rotate(calc((var(--i)-1.5)*6deg))]" style="--i:3" aria-hidden="true">&spades;</div>
    </div>
  </div>
</section>
```
## CSS
```css
@import "tailwindcss";

@theme {
  --color-stk17-bg: #131620;
  --color-stk17-bg: oklch(0.21 0.018 275);
  --color-stk17-ring: #45ab7c;
  --color-stk17-ring: oklch(0.66 0.15 150);
  --color-stk17-c1: #cf5f4b;
  --color-stk17-c1: oklch(0.62 0.16 20);
  --color-stk17-c2: #45ab7c;
  --color-stk17-c2: oklch(0.64 0.15 150);
  --color-stk17-c3: #4d8ade;
  --color-stk17-c3: oklch(0.62 0.16 250);
  --color-stk17-c4: #9b6ed6;
  --color-stk17-c4: oklch(0.62 0.16 300);
  --font-stk17-sans: 'Archivo', system-ui, sans-serif;
  --animate-stk17-riffle: stk-17-riffle 1.6s ease-in-out infinite;
}
/* The riffle path itself. z-index is not interpolated inside keyframes — it
   steps at each stop, which is exactly what a shuffle wants. */

@keyframes stk-17-riffle {
  0%,
    100% {
    transform: translateX(0) rotate(0);
    z-index: 2;
  }

  25% {
    transform: translateX(70px) rotate(10deg);
    z-index: 5;
  }

  50% {
    transform: translateX(0) rotate(0);
    z-index: 2;
  }

  75% {
    transform: translateX(-70px) rotate(-10deg);
    z-index: 1;
  }
}
```

How this works

Negative delay is the whole trick. A positive delay staggers when each card starts; a negative one starts it already part-way through, which is what makes four cards on one keyframe look like a continuous riffle rather than a chorus line:

animate-stk17-riffle [animation-delay:calc(var(--i)*-0.4s)]

Four :nth-child delay rules become one class reading the index. animation-delay does have a utility scale in v4, but not one that takes a calc() of a custom property, so the arbitrary form is the right call.

Pausing is not stopping. [animation-play-state:paused] freezes the keyframe where it is, and the static fan transform then overrides the frozen position — so hovering both stops the motion and lays the cards out flat. Removing the animation instead would snap the cards back to their unanimated positions first.

Decorative loops need a real off-switch. Continuous motion with no user intent behind it is the clearest case in the WCAG motion guidance. Under motion-reduce: the animation is none and the fan is applied unconditionally, so the deck arrives readable and completely still.

Make it yours

  • Speed the riffle by editing the duration in --animate-stk17-riffle.
  • Change the shuffle path in the @keyframes block — the one part that cannot be a utility.
  • Set the hover fan with the 54px and 6deg multipliers; the 1.5 centres it for four cards (use (n-1)/2 for n cards).
  • Recolour the faces through the four bg-stk17-* tokens.
  • Add a card by giving it the next --i and updating the centring constant.

Gotchas — read before shipping

  • The delay must be negative. A positive one staggers the starts and the deck looks like it is queueing, not riffling.
  • Pause, do not remove. animate-none on hover snaps every card back to its resting transform before the fan applies.
  • Always ship the reduced-motion off-switch for a decorative loop — and ship the readable state, not a slower loop.
  • z-index inside @keyframes is not interpolated; it steps at each keyframe, which is exactly what a riffle wants.
  • Keep the loop pausable by pointer and keyboard: group-focus-visible: alongside group-hover:.
  • The stage needs no fixed aspect, but it does need a height — the cards are absolutely positioned and contribute nothing to it.

Browser support

ChromeSafariFirefoxEdge
111+16.4+128+111+

CSS keyframes, negative animation-delay and animation-play-state 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

Plain CSSno Tailwind surface

HTML & JSno Tailwind involved

Search CodeFronts

Loading…