30 / 36
All 36 in 36 Tailwind Stacked Cards
- 01Tailwind Scroll-Driven Scaling Card Stack
- 02Tailwind Swipe Card Deck Drag to Dismiss
- 03Tailwind Glassmorphism Testimonial Card Stack
- 04Tailwind Stacked Cards Expand Accordion on Click
- 05Tailwind Fan Out Photo Stack on Hover
- 06Tailwind 3D Perspective Tilt Card Stack
- 07Tailwind Multi Step Form Card Stack
- 08Tailwind Tabbed Card Stack Bring to Front
- 09Tailwind Click to Front Stacked Cards Cycle
- 10Tailwind Classic Card Deck Stack Shadow
- 11Tailwind Fan Spread Hand of Cards
- 12Tailwind Polaroid Photo Stack Scattered Rotate
- 13Tailwind Staircase Offset Stacked Cards Cascade
- 14Tailwind Cards Spread Out Horizontally on Hover
- 15Tailwind Stacked Cards Lift on Hover Sibling Push
- 16Tailwind Accordion Stacked Cards Slide Out Menu
- 17Tailwind Shuffle Cards Animation Loop Deck
- 18Tailwind File Folder Tab Stacked Cards
- 19Tailwind Scattered Photos Pinch to Centre on Hover
- 20Tailwind Mobile Swipe Card Stack Translate3d
- 21Tailwind Single Accordion Card Nested Layers
- 22Tailwind Isometric Tower Stacked Cards 3D Column
- 23Tailwind 3D Flip Card Stack Reveal Back
- 24Tailwind Card Stack Tilt on Hover Quadrant
- 25Tailwind Notification Stack Pile Dismiss
- 26Tailwind Receipt Roll Unroll Stacked Items
- 27Tailwind Scratch Card Reveal Canvas Mask
- 28Tailwind Stacked Cards Hover Reveal Spread
- 29Tailwind Scroll Activated Sticky Stacking Cards
- 30Tailwind Stacked Testimonial Slider Radio
- 31Tailwind Isometric Overlapping Stacked Cards
- 32Tailwind Bento Grid Stacked Cards Hover Fan
- 33Tailwind Perspective Card Deck Depth Tunnel
- 34Tailwind Peel Back Corner Card Reveal
- 35Tailwind Timeline Stacked Milestone Cards
- 36Tailwind Spiral Tower Rotating Stacked Cards
Tailwind Stacked Testimonial Slider Radio
Three reviews, a cyclic stack, no JavaScript — and the port's headline feature is named peers. The mapping here is a rotation (radio 1 puts quote 2 second and quote 3 third; radio 2 rotates it on), which nesting cannot solve because each quote must respond to all three radios. peer/t1, peer/t2, peer/t3 plus peer-checked/t2: expresses it exactly, and nine positional selectors disappear.
Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time
Untitled UI React · Affiliate link
The code
<section class="stk-30 grid w-full min-h-svh place-items-center px-5 py-15 font-stk30-sans bg-linear-[160deg,var(--color-stk30-p1),var(--color-stk30-p2)]" aria-label="Customer testimonials">
<div class="relative grid w-[min(330px,88vw)] grid-cols-[1fr_auto_auto_auto_1fr] justify-items-center gap-y-7">
<input class="peer/t1 absolute size-0 opacity-0" type="radio" name="stk-30" id="stk-30-t1" checked aria-label="Testimonial 1">
<input class="peer/t2 absolute size-0 opacity-0" type="radio" name="stk-30" id="stk-30-t2" aria-label="Testimonial 2">
<input class="peer/t3 absolute size-0 opacity-0" type="radio" name="stk-30" id="stk-30-t3" aria-label="Testimonial 3">
<figure class="col-span-5 col-start-1 row-start-1 flex h-75 w-full flex-col justify-between rounded-[22px] border border-stk30-line bg-stk30-card p-7.5 opacity-0 shadow-[0_18px_40px_rgba(120,80,50,0.18)] translate-y-7 scale-[0.88] z-1 transition-[transform,opacity] duration-600 ease-stk30 peer-checked/t1:z-4 peer-checked/t1:translate-y-0 peer-checked/t1:scale-100 peer-checked/t1:opacity-100 peer-checked/t3:z-3 peer-checked/t3:translate-y-3.5 peer-checked/t3:scale-[0.94] peer-checked/t3:opacity-55 peer-checked/t2:z-2 peer-checked/t2:translate-y-7 peer-checked/t2:scale-[0.88] peer-checked/t2:opacity-30 motion-reduce:transition-none">
<blockquote class="m-0 font-stk30-serif text-xl italic leading-snug text-stk30-ink">“This completely changed how our team ships. We cut launch time in half.”</blockquote>
<figcaption class="flex items-center gap-3">
<span class="block size-11 shrink-0 rounded-full bg-linear-[135deg,var(--color-stk30-av1a),var(--color-stk30-av1b)]" aria-hidden="true"></span>
<span><b class="block text-[0.9rem] font-semibold text-stk30-name">Maya Chen</b><span class="text-[0.78rem] text-stk30-meta">Head of Product, Loop</span></span>
</figcaption>
</figure>
<figure class="col-span-5 col-start-1 row-start-1 flex h-75 w-full flex-col justify-between rounded-[22px] border border-stk30-line bg-stk30-card p-7.5 opacity-0 shadow-[0_18px_40px_rgba(120,80,50,0.18)] translate-y-7 scale-[0.88] z-1 transition-[transform,opacity] duration-600 ease-stk30 peer-checked/t2:z-4 peer-checked/t2:translate-y-0 peer-checked/t2:scale-100 peer-checked/t2:opacity-100 peer-checked/t1:z-3 peer-checked/t1:translate-y-3.5 peer-checked/t1:scale-[0.94] peer-checked/t1:opacity-55 peer-checked/t3:z-2 peer-checked/t3:translate-y-7 peer-checked/t3:scale-[0.88] peer-checked/t3:opacity-30 motion-reduce:transition-none">
<blockquote class="m-0 font-stk30-serif text-xl italic leading-snug text-stk30-ink">“Beautiful, fast, and genuinely a joy to use every single day.”</blockquote>
<figcaption class="flex items-center gap-3">
<span class="block size-11 shrink-0 rounded-full bg-linear-[135deg,var(--color-stk30-av2a),var(--color-stk30-av2b)]" aria-hidden="true"></span>
<span><b class="block text-[0.9rem] font-semibold text-stk30-name">Theo Marsh</b><span class="text-[0.78rem] text-stk30-meta">Founder, Northbeam</span></span>
</figcaption>
</figure>
<figure class="col-span-5 col-start-1 row-start-1 flex h-75 w-full flex-col justify-between rounded-[22px] border border-stk30-line bg-stk30-card p-7.5 opacity-0 shadow-[0_18px_40px_rgba(120,80,50,0.18)] translate-y-7 scale-[0.88] z-1 transition-[transform,opacity] duration-600 ease-stk30 peer-checked/t3:z-4 peer-checked/t3:translate-y-0 peer-checked/t3:scale-100 peer-checked/t3:opacity-100 peer-checked/t2:z-3 peer-checked/t2:translate-y-3.5 peer-checked/t2:scale-[0.94] peer-checked/t2:opacity-55 peer-checked/t1:z-2 peer-checked/t1:translate-y-7 peer-checked/t1:scale-[0.88] peer-checked/t1:opacity-30 motion-reduce:transition-none">
<blockquote class="m-0 font-stk30-serif text-xl italic leading-snug text-stk30-ink">“The support alone is worth it. Best decision we made this year.”</blockquote>
<figcaption class="flex items-center gap-3">
<span class="block size-11 shrink-0 rounded-full bg-linear-[135deg,var(--color-stk30-av3a),var(--color-stk30-av3b)]" aria-hidden="true"></span>
<span><b class="block text-[0.9rem] font-semibold text-stk30-name">Lena Ortiz</b><span class="text-[0.78rem] text-stk30-meta">CTO, Brightside</span></span>
</figcaption>
</figure>
<label class="col-start-2 row-start-2 grid size-11 cursor-pointer place-items-center rounded-full before:block before:size-2.75 before:rounded-full before:bg-stk30-dot before:transition-[background-color,transform] before:duration-300 hover:before:bg-stk30-dothover peer-checked/t1:before:scale-130 peer-checked/t1:before:bg-stk30-active peer-focus-visible/t1:outline-3 peer-focus-visible/t1:outline-stk30-active" for="stk-30-t1">
<span class="sr-only">Show testimonial 1</span>
</label>
<label class="col-start-3 row-start-2 grid size-11 cursor-pointer place-items-center rounded-full before:block before:size-2.75 before:rounded-full before:bg-stk30-dot before:transition-[background-color,transform] before:duration-300 hover:before:bg-stk30-dothover peer-checked/t2:before:scale-130 peer-checked/t2:before:bg-stk30-active peer-focus-visible/t2:outline-3 peer-focus-visible/t2:outline-stk30-active" for="stk-30-t2">
<span class="sr-only">Show testimonial 2</span>
</label>
<label class="col-start-4 row-start-2 grid size-11 cursor-pointer place-items-center rounded-full before:block before:size-2.75 before:rounded-full before:bg-stk30-dot before:transition-[background-color,transform] before:duration-300 hover:before:bg-stk30-dothover peer-checked/t3:before:scale-130 peer-checked/t3:before:bg-stk30-active peer-focus-visible/t3:outline-3 peer-focus-visible/t3:outline-stk30-active" for="stk-30-t3">
<span class="sr-only">Show testimonial 3</span>
</label>
</div>
</section><section class="stk-30 grid w-full min-h-svh place-items-center px-5 py-15 font-stk30-sans bg-linear-[160deg,var(--color-stk30-p1),var(--color-stk30-p2)]" aria-label="Customer testimonials">
<div class="relative grid w-[min(330px,88vw)] grid-cols-[1fr_auto_auto_auto_1fr] justify-items-center gap-y-7">
<input class="peer/t1 absolute size-0 opacity-0" type="radio" name="stk-30" id="stk-30-t1" checked aria-label="Testimonial 1">
<input class="peer/t2 absolute size-0 opacity-0" type="radio" name="stk-30" id="stk-30-t2" aria-label="Testimonial 2">
<input class="peer/t3 absolute size-0 opacity-0" type="radio" name="stk-30" id="stk-30-t3" aria-label="Testimonial 3">
<figure class="col-span-5 col-start-1 row-start-1 flex h-75 w-full flex-col justify-between rounded-[22px] border border-stk30-line bg-stk30-card p-7.5 opacity-0 shadow-[0_18px_40px_rgba(120,80,50,0.18)] translate-y-7 scale-[0.88] z-1 transition-[transform,opacity] duration-600 ease-stk30 peer-checked/t1:z-4 peer-checked/t1:translate-y-0 peer-checked/t1:scale-100 peer-checked/t1:opacity-100 peer-checked/t3:z-3 peer-checked/t3:translate-y-3.5 peer-checked/t3:scale-[0.94] peer-checked/t3:opacity-55 peer-checked/t2:z-2 peer-checked/t2:translate-y-7 peer-checked/t2:scale-[0.88] peer-checked/t2:opacity-30 motion-reduce:transition-none">
<blockquote class="m-0 font-stk30-serif text-xl italic leading-snug text-stk30-ink">“This completely changed how our team ships. We cut launch time in half.”</blockquote>
<figcaption class="flex items-center gap-3">
<span class="block size-11 shrink-0 rounded-full bg-linear-[135deg,var(--color-stk30-av1a),var(--color-stk30-av1b)]" aria-hidden="true"></span>
<span><b class="block text-[0.9rem] font-semibold text-stk30-name">Maya Chen</b><span class="text-[0.78rem] text-stk30-meta">Head of Product, Loop</span></span>
</figcaption>
</figure>
<figure class="col-span-5 col-start-1 row-start-1 flex h-75 w-full flex-col justify-between rounded-[22px] border border-stk30-line bg-stk30-card p-7.5 opacity-0 shadow-[0_18px_40px_rgba(120,80,50,0.18)] translate-y-7 scale-[0.88] z-1 transition-[transform,opacity] duration-600 ease-stk30 peer-checked/t2:z-4 peer-checked/t2:translate-y-0 peer-checked/t2:scale-100 peer-checked/t2:opacity-100 peer-checked/t1:z-3 peer-checked/t1:translate-y-3.5 peer-checked/t1:scale-[0.94] peer-checked/t1:opacity-55 peer-checked/t3:z-2 peer-checked/t3:translate-y-7 peer-checked/t3:scale-[0.88] peer-checked/t3:opacity-30 motion-reduce:transition-none">
<blockquote class="m-0 font-stk30-serif text-xl italic leading-snug text-stk30-ink">“Beautiful, fast, and genuinely a joy to use every single day.”</blockquote>
<figcaption class="flex items-center gap-3">
<span class="block size-11 shrink-0 rounded-full bg-linear-[135deg,var(--color-stk30-av2a),var(--color-stk30-av2b)]" aria-hidden="true"></span>
<span><b class="block text-[0.9rem] font-semibold text-stk30-name">Theo Marsh</b><span class="text-[0.78rem] text-stk30-meta">Founder, Northbeam</span></span>
</figcaption>
</figure>
<figure class="col-span-5 col-start-1 row-start-1 flex h-75 w-full flex-col justify-between rounded-[22px] border border-stk30-line bg-stk30-card p-7.5 opacity-0 shadow-[0_18px_40px_rgba(120,80,50,0.18)] translate-y-7 scale-[0.88] z-1 transition-[transform,opacity] duration-600 ease-stk30 peer-checked/t3:z-4 peer-checked/t3:translate-y-0 peer-checked/t3:scale-100 peer-checked/t3:opacity-100 peer-checked/t2:z-3 peer-checked/t2:translate-y-3.5 peer-checked/t2:scale-[0.94] peer-checked/t2:opacity-55 peer-checked/t1:z-2 peer-checked/t1:translate-y-7 peer-checked/t1:scale-[0.88] peer-checked/t1:opacity-30 motion-reduce:transition-none">
<blockquote class="m-0 font-stk30-serif text-xl italic leading-snug text-stk30-ink">“The support alone is worth it. Best decision we made this year.”</blockquote>
<figcaption class="flex items-center gap-3">
<span class="block size-11 shrink-0 rounded-full bg-linear-[135deg,var(--color-stk30-av3a),var(--color-stk30-av3b)]" aria-hidden="true"></span>
<span><b class="block text-[0.9rem] font-semibold text-stk30-name">Lena Ortiz</b><span class="text-[0.78rem] text-stk30-meta">CTO, Brightside</span></span>
</figcaption>
</figure>
<label class="col-start-2 row-start-2 grid size-11 cursor-pointer place-items-center rounded-full before:block before:size-2.75 before:rounded-full before:bg-stk30-dot before:transition-[background-color,transform] before:duration-300 hover:before:bg-stk30-dothover peer-checked/t1:before:scale-130 peer-checked/t1:before:bg-stk30-active peer-focus-visible/t1:outline-3 peer-focus-visible/t1:outline-stk30-active" for="stk-30-t1">
<span class="sr-only">Show testimonial 1</span>
</label>
<label class="col-start-3 row-start-2 grid size-11 cursor-pointer place-items-center rounded-full before:block before:size-2.75 before:rounded-full before:bg-stk30-dot before:transition-[background-color,transform] before:duration-300 hover:before:bg-stk30-dothover peer-checked/t2:before:scale-130 peer-checked/t2:before:bg-stk30-active peer-focus-visible/t2:outline-3 peer-focus-visible/t2:outline-stk30-active" for="stk-30-t2">
<span class="sr-only">Show testimonial 2</span>
</label>
<label class="col-start-4 row-start-2 grid size-11 cursor-pointer place-items-center rounded-full before:block before:size-2.75 before:rounded-full before:bg-stk30-dot before:transition-[background-color,transform] before:duration-300 hover:before:bg-stk30-dothover peer-checked/t3:before:scale-130 peer-checked/t3:before:bg-stk30-active peer-focus-visible/t3:outline-3 peer-focus-visible/t3:outline-stk30-active" for="stk-30-t3">
<span class="sr-only">Show testimonial 3</span>
</label>
</div>
</section>@import "tailwindcss";
@theme {
--color-stk30-p1: #fef6ee;
--color-stk30-p1: oklch(0.97 0.016 75);
--color-stk30-p2: #f4e4d4;
--color-stk30-p2: oklch(0.92 0.030 70);
--color-stk30-card: #fffdfa;
--color-stk30-card: oklch(0.99 0.006 80);
--color-stk30-line: #f0e2d2;
--color-stk30-line: oklch(0.93 0.020 75);
--color-stk30-ink: #3a2e24;
--color-stk30-ink: oklch(0.33 0.022 60);
--color-stk30-name: #2a201a;
--color-stk30-name: oklch(0.26 0.020 55);
--color-stk30-meta: #9b7d5f;
--color-stk30-meta: oklch(0.62 0.045 65);
--color-stk30-dot: #d8c3aa;
--color-stk30-dot: oklch(0.83 0.035 75);
--color-stk30-dothover: #c9a98a;
--color-stk30-dothover: oklch(0.76 0.050 70);
--color-stk30-active: #b8553f;
--color-stk30-active: oklch(0.55 0.145 35);
--color-stk30-av1a: #ff9a3c;
--color-stk30-av1b: #ff5e62;
--color-stk30-av2a: #43cea2;
--color-stk30-av2b: #185a9d;
--color-stk30-av3a: #c471f5;
--color-stk30-av3b: #7f53ac;
--font-stk30-sans: 'Geist', system-ui, sans-serif;
--font-stk30-serif: 'Newsreader', Georgia, serif;
--ease-stk30: cubic-bezier(0.3, 0.9, 0.3, 1);
}@import "tailwindcss";
@theme {
--color-stk30-p1: #fef6ee;
--color-stk30-p1: oklch(0.97 0.016 75);
--color-stk30-p2: #f4e4d4;
--color-stk30-p2: oklch(0.92 0.030 70);
--color-stk30-card: #fffdfa;
--color-stk30-card: oklch(0.99 0.006 80);
--color-stk30-line: #f0e2d2;
--color-stk30-line: oklch(0.93 0.020 75);
--color-stk30-ink: #3a2e24;
--color-stk30-ink: oklch(0.33 0.022 60);
--color-stk30-name: #2a201a;
--color-stk30-name: oklch(0.26 0.020 55);
--color-stk30-meta: #9b7d5f;
--color-stk30-meta: oklch(0.62 0.045 65);
--color-stk30-dot: #d8c3aa;
--color-stk30-dot: oklch(0.83 0.035 75);
--color-stk30-dothover: #c9a98a;
--color-stk30-dothover: oklch(0.76 0.050 70);
--color-stk30-active: #b8553f;
--color-stk30-active: oklch(0.55 0.145 35);
--color-stk30-av1a: #ff9a3c;
--color-stk30-av1b: #ff5e62;
--color-stk30-av2a: #43cea2;
--color-stk30-av2b: #185a9d;
--color-stk30-av3a: #c471f5;
--color-stk30-av3b: #7f53ac;
--font-stk30-sans: 'Geist', system-ui, sans-serif;
--font-stk30-serif: 'Newsreader', Georgia, serif;
--ease-stk30: cubic-bezier(0.3, 0.9, 0.3, 1);
}Setup — do this once per project
Skip this if your project already runs Tailwind v4.
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<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.
npm install tailwindcss @tailwindcss/vite
npm install tailwindcss @tailwindcss/vite// vite.config.js ("type": "module" in package.json)
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [tailwindcss()],
})// vite.config.js ("type": "module" in package.json)
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [tailwindcss()],
})Any v4 setup works — Vite, PostCSS or the CLI. Nothing in this demo is bundler-specific.
@import "tailwindcss";
@theme {
--color-stk30-p1: #fef6ee;
--color-stk30-p1: oklch(0.97 0.016 75);
--color-stk30-p2: #f4e4d4;
--color-stk30-p2: oklch(0.92 0.030 70);
--color-stk30-card: #fffdfa;
--color-stk30-card: oklch(0.99 0.006 80);
--color-stk30-line: #f0e2d2;
--color-stk30-line: oklch(0.93 0.020 75);
--color-stk30-ink: #3a2e24;
--color-stk30-ink: oklch(0.33 0.022 60);
--color-stk30-name: #2a201a;
--color-stk30-name: oklch(0.26 0.020 55);
--color-stk30-meta: #9b7d5f;
--color-stk30-meta: oklch(0.62 0.045 65);
--color-stk30-dot: #d8c3aa;
--color-stk30-dot: oklch(0.83 0.035 75);
--color-stk30-dothover: #c9a98a;
--color-stk30-dothover: oklch(0.76 0.050 70);
--color-stk30-active: #b8553f;
--color-stk30-active: oklch(0.55 0.145 35);
--color-stk30-av1a: #ff9a3c;
--color-stk30-av1b: #ff5e62;
--color-stk30-av2a: #43cea2;
--color-stk30-av2b: #185a9d;
--color-stk30-av3a: #c471f5;
--color-stk30-av3b: #7f53ac;
--font-stk30-sans: 'Geist', system-ui, sans-serif;
--font-stk30-serif: 'Newsreader', Georgia, serif;
--ease-stk30: cubic-bezier(0.3, 0.9, 0.3, 1);
}@import "tailwindcss";
@theme {
--color-stk30-p1: #fef6ee;
--color-stk30-p1: oklch(0.97 0.016 75);
--color-stk30-p2: #f4e4d4;
--color-stk30-p2: oklch(0.92 0.030 70);
--color-stk30-card: #fffdfa;
--color-stk30-card: oklch(0.99 0.006 80);
--color-stk30-line: #f0e2d2;
--color-stk30-line: oklch(0.93 0.020 75);
--color-stk30-ink: #3a2e24;
--color-stk30-ink: oklch(0.33 0.022 60);
--color-stk30-name: #2a201a;
--color-stk30-name: oklch(0.26 0.020 55);
--color-stk30-meta: #9b7d5f;
--color-stk30-meta: oklch(0.62 0.045 65);
--color-stk30-dot: #d8c3aa;
--color-stk30-dot: oklch(0.83 0.035 75);
--color-stk30-dothover: #c9a98a;
--color-stk30-dothover: oklch(0.76 0.050 70);
--color-stk30-active: #b8553f;
--color-stk30-active: oklch(0.55 0.145 35);
--color-stk30-av1a: #ff9a3c;
--color-stk30-av1b: #ff5e62;
--color-stk30-av2a: #43cea2;
--color-stk30-av2b: #185a9d;
--color-stk30-av3a: #c471f5;
--color-stk30-av3b: #7f53ac;
--font-stk30-sans: 'Geist', system-ui, sans-serif;
--font-stk30-serif: 'Newsreader', Georgia, serif;
--ease-stk30: cubic-bezier(0.3, 0.9, 0.3, 1);
}<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=Newsreader:ital,opsz,wght@0,6..72,500;1,6..72,400&family=Geist:wght@400;500;600&display=swap">
<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=Newsreader:ital,opsz,wght@0,6..72,500;1,6..72,400&family=Geist:wght@400;500;600&display=swap">What changed in the port
Which parts became utilities, which stayed CSS, and why.
- The webfonts load as
<link>tags insetup, not as an@importinside the stylesheet. Tailwind emits user@importrules after its own@layerblocks, and a late@importis invalid CSS — the browser drops it and the demo silently falls back to system fonts. - Named peers (
peer/t1…peer-checked/t2:) are the headline of this port: they express a many-to-many state mapping that nesting cannot, and they retire nine grouped selectors. - Each quote now carries all three of its poses, so a card's whole life in the rotation is readable on the card rather than assembled from three selector groups.
- The dots are
<label>s whose active state comes from the same named peer, so the indicator can never disagree with the stack. - The dot itself is a
before:pseudo-element inside asize-11label — an 11px visual with a 44px hit area, which the CSS version's bare 11px label does not have. - The radios are
absolute size-0 opacity-0: hidden but focusable, so native arrow-key cycling through the group still works. - Testimonial copy is a compliance surface under the FTC Endorsements Rule — these are illustrative and must be replaced with real, substantiated quotes before shipping.
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 Stacked Testimonial Slider Radio
Source: https://codefronts.com/components/tailwind-stacked-cards/tailwind-stacked-testimonial-slider-radio/
Three reviews, a cyclic stack, no JavaScript — and the port's headline feature is named peers. The mapping here is a rotation (radio 1 puts quote 2 second and quote 3 third; radio 2 rotates it on), which nesting cannot solve because each quote must respond to all three radios. peer/t1, peer/t2, peer/t3 plus peer-checked/t2: expresses it exactly, and nine positional selectors disappear.
## HTML
```html
<section class="stk-30 grid w-full min-h-svh place-items-center px-5 py-15 font-stk30-sans bg-linear-[160deg,var(--color-stk30-p1),var(--color-stk30-p2)]" aria-label="Customer testimonials">
<div class="relative grid w-[min(330px,88vw)] grid-cols-[1fr_auto_auto_auto_1fr] justify-items-center gap-y-7">
<input class="peer/t1 absolute size-0 opacity-0" type="radio" name="stk-30" id="stk-30-t1" checked aria-label="Testimonial 1">
<input class="peer/t2 absolute size-0 opacity-0" type="radio" name="stk-30" id="stk-30-t2" aria-label="Testimonial 2">
<input class="peer/t3 absolute size-0 opacity-0" type="radio" name="stk-30" id="stk-30-t3" aria-label="Testimonial 3">
<figure class="col-span-5 col-start-1 row-start-1 flex h-75 w-full flex-col justify-between rounded-[22px] border border-stk30-line bg-stk30-card p-7.5 opacity-0 shadow-[0_18px_40px_rgba(120,80,50,0.18)] translate-y-7 scale-[0.88] z-1 transition-[transform,opacity] duration-600 ease-stk30 peer-checked/t1:z-4 peer-checked/t1:translate-y-0 peer-checked/t1:scale-100 peer-checked/t1:opacity-100 peer-checked/t3:z-3 peer-checked/t3:translate-y-3.5 peer-checked/t3:scale-[0.94] peer-checked/t3:opacity-55 peer-checked/t2:z-2 peer-checked/t2:translate-y-7 peer-checked/t2:scale-[0.88] peer-checked/t2:opacity-30 motion-reduce:transition-none">
<blockquote class="m-0 font-stk30-serif text-xl italic leading-snug text-stk30-ink">“This completely changed how our team ships. We cut launch time in half.”</blockquote>
<figcaption class="flex items-center gap-3">
<span class="block size-11 shrink-0 rounded-full bg-linear-[135deg,var(--color-stk30-av1a),var(--color-stk30-av1b)]" aria-hidden="true"></span>
<span><b class="block text-[0.9rem] font-semibold text-stk30-name">Maya Chen</b><span class="text-[0.78rem] text-stk30-meta">Head of Product, Loop</span></span>
</figcaption>
</figure>
<figure class="col-span-5 col-start-1 row-start-1 flex h-75 w-full flex-col justify-between rounded-[22px] border border-stk30-line bg-stk30-card p-7.5 opacity-0 shadow-[0_18px_40px_rgba(120,80,50,0.18)] translate-y-7 scale-[0.88] z-1 transition-[transform,opacity] duration-600 ease-stk30 peer-checked/t2:z-4 peer-checked/t2:translate-y-0 peer-checked/t2:scale-100 peer-checked/t2:opacity-100 peer-checked/t1:z-3 peer-checked/t1:translate-y-3.5 peer-checked/t1:scale-[0.94] peer-checked/t1:opacity-55 peer-checked/t3:z-2 peer-checked/t3:translate-y-7 peer-checked/t3:scale-[0.88] peer-checked/t3:opacity-30 motion-reduce:transition-none">
<blockquote class="m-0 font-stk30-serif text-xl italic leading-snug text-stk30-ink">“Beautiful, fast, and genuinely a joy to use every single day.”</blockquote>
<figcaption class="flex items-center gap-3">
<span class="block size-11 shrink-0 rounded-full bg-linear-[135deg,var(--color-stk30-av2a),var(--color-stk30-av2b)]" aria-hidden="true"></span>
<span><b class="block text-[0.9rem] font-semibold text-stk30-name">Theo Marsh</b><span class="text-[0.78rem] text-stk30-meta">Founder, Northbeam</span></span>
</figcaption>
</figure>
<figure class="col-span-5 col-start-1 row-start-1 flex h-75 w-full flex-col justify-between rounded-[22px] border border-stk30-line bg-stk30-card p-7.5 opacity-0 shadow-[0_18px_40px_rgba(120,80,50,0.18)] translate-y-7 scale-[0.88] z-1 transition-[transform,opacity] duration-600 ease-stk30 peer-checked/t3:z-4 peer-checked/t3:translate-y-0 peer-checked/t3:scale-100 peer-checked/t3:opacity-100 peer-checked/t2:z-3 peer-checked/t2:translate-y-3.5 peer-checked/t2:scale-[0.94] peer-checked/t2:opacity-55 peer-checked/t1:z-2 peer-checked/t1:translate-y-7 peer-checked/t1:scale-[0.88] peer-checked/t1:opacity-30 motion-reduce:transition-none">
<blockquote class="m-0 font-stk30-serif text-xl italic leading-snug text-stk30-ink">“The support alone is worth it. Best decision we made this year.”</blockquote>
<figcaption class="flex items-center gap-3">
<span class="block size-11 shrink-0 rounded-full bg-linear-[135deg,var(--color-stk30-av3a),var(--color-stk30-av3b)]" aria-hidden="true"></span>
<span><b class="block text-[0.9rem] font-semibold text-stk30-name">Lena Ortiz</b><span class="text-[0.78rem] text-stk30-meta">CTO, Brightside</span></span>
</figcaption>
</figure>
<label class="col-start-2 row-start-2 grid size-11 cursor-pointer place-items-center rounded-full before:block before:size-2.75 before:rounded-full before:bg-stk30-dot before:transition-[background-color,transform] before:duration-300 hover:before:bg-stk30-dothover peer-checked/t1:before:scale-130 peer-checked/t1:before:bg-stk30-active peer-focus-visible/t1:outline-3 peer-focus-visible/t1:outline-stk30-active" for="stk-30-t1">
<span class="sr-only">Show testimonial 1</span>
</label>
<label class="col-start-3 row-start-2 grid size-11 cursor-pointer place-items-center rounded-full before:block before:size-2.75 before:rounded-full before:bg-stk30-dot before:transition-[background-color,transform] before:duration-300 hover:before:bg-stk30-dothover peer-checked/t2:before:scale-130 peer-checked/t2:before:bg-stk30-active peer-focus-visible/t2:outline-3 peer-focus-visible/t2:outline-stk30-active" for="stk-30-t2">
<span class="sr-only">Show testimonial 2</span>
</label>
<label class="col-start-4 row-start-2 grid size-11 cursor-pointer place-items-center rounded-full before:block before:size-2.75 before:rounded-full before:bg-stk30-dot before:transition-[background-color,transform] before:duration-300 hover:before:bg-stk30-dothover peer-checked/t3:before:scale-130 peer-checked/t3:before:bg-stk30-active peer-focus-visible/t3:outline-3 peer-focus-visible/t3:outline-stk30-active" for="stk-30-t3">
<span class="sr-only">Show testimonial 3</span>
</label>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-stk30-p1: #fef6ee;
--color-stk30-p1: oklch(0.97 0.016 75);
--color-stk30-p2: #f4e4d4;
--color-stk30-p2: oklch(0.92 0.030 70);
--color-stk30-card: #fffdfa;
--color-stk30-card: oklch(0.99 0.006 80);
--color-stk30-line: #f0e2d2;
--color-stk30-line: oklch(0.93 0.020 75);
--color-stk30-ink: #3a2e24;
--color-stk30-ink: oklch(0.33 0.022 60);
--color-stk30-name: #2a201a;
--color-stk30-name: oklch(0.26 0.020 55);
--color-stk30-meta: #9b7d5f;
--color-stk30-meta: oklch(0.62 0.045 65);
--color-stk30-dot: #d8c3aa;
--color-stk30-dot: oklch(0.83 0.035 75);
--color-stk30-dothover: #c9a98a;
--color-stk30-dothover: oklch(0.76 0.050 70);
--color-stk30-active: #b8553f;
--color-stk30-active: oklch(0.55 0.145 35);
--color-stk30-av1a: #ff9a3c;
--color-stk30-av1b: #ff5e62;
--color-stk30-av2a: #43cea2;
--color-stk30-av2b: #185a9d;
--color-stk30-av3a: #c471f5;
--color-stk30-av3b: #7f53ac;
--font-stk30-sans: 'Geist', system-ui, sans-serif;
--font-stk30-serif: 'Newsreader', Georgia, serif;
--ease-stk30: cubic-bezier(0.3, 0.9, 0.3, 1);
}
```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 Stacked Testimonial Slider Radio
Source: https://codefronts.com/components/tailwind-stacked-cards/tailwind-stacked-testimonial-slider-radio/
Three reviews, a cyclic stack, no JavaScript — and the port's headline feature is named peers. The mapping here is a rotation (radio 1 puts quote 2 second and quote 3 third; radio 2 rotates it on), which nesting cannot solve because each quote must respond to all three radios. peer/t1, peer/t2, peer/t3 plus peer-checked/t2: expresses it exactly, and nine positional selectors disappear.
## HTML
```html
<section class="stk-30 grid w-full min-h-svh place-items-center px-5 py-15 font-stk30-sans bg-linear-[160deg,var(--color-stk30-p1),var(--color-stk30-p2)]" aria-label="Customer testimonials">
<div class="relative grid w-[min(330px,88vw)] grid-cols-[1fr_auto_auto_auto_1fr] justify-items-center gap-y-7">
<input class="peer/t1 absolute size-0 opacity-0" type="radio" name="stk-30" id="stk-30-t1" checked aria-label="Testimonial 1">
<input class="peer/t2 absolute size-0 opacity-0" type="radio" name="stk-30" id="stk-30-t2" aria-label="Testimonial 2">
<input class="peer/t3 absolute size-0 opacity-0" type="radio" name="stk-30" id="stk-30-t3" aria-label="Testimonial 3">
<figure class="col-span-5 col-start-1 row-start-1 flex h-75 w-full flex-col justify-between rounded-[22px] border border-stk30-line bg-stk30-card p-7.5 opacity-0 shadow-[0_18px_40px_rgba(120,80,50,0.18)] translate-y-7 scale-[0.88] z-1 transition-[transform,opacity] duration-600 ease-stk30 peer-checked/t1:z-4 peer-checked/t1:translate-y-0 peer-checked/t1:scale-100 peer-checked/t1:opacity-100 peer-checked/t3:z-3 peer-checked/t3:translate-y-3.5 peer-checked/t3:scale-[0.94] peer-checked/t3:opacity-55 peer-checked/t2:z-2 peer-checked/t2:translate-y-7 peer-checked/t2:scale-[0.88] peer-checked/t2:opacity-30 motion-reduce:transition-none">
<blockquote class="m-0 font-stk30-serif text-xl italic leading-snug text-stk30-ink">“This completely changed how our team ships. We cut launch time in half.”</blockquote>
<figcaption class="flex items-center gap-3">
<span class="block size-11 shrink-0 rounded-full bg-linear-[135deg,var(--color-stk30-av1a),var(--color-stk30-av1b)]" aria-hidden="true"></span>
<span><b class="block text-[0.9rem] font-semibold text-stk30-name">Maya Chen</b><span class="text-[0.78rem] text-stk30-meta">Head of Product, Loop</span></span>
</figcaption>
</figure>
<figure class="col-span-5 col-start-1 row-start-1 flex h-75 w-full flex-col justify-between rounded-[22px] border border-stk30-line bg-stk30-card p-7.5 opacity-0 shadow-[0_18px_40px_rgba(120,80,50,0.18)] translate-y-7 scale-[0.88] z-1 transition-[transform,opacity] duration-600 ease-stk30 peer-checked/t2:z-4 peer-checked/t2:translate-y-0 peer-checked/t2:scale-100 peer-checked/t2:opacity-100 peer-checked/t1:z-3 peer-checked/t1:translate-y-3.5 peer-checked/t1:scale-[0.94] peer-checked/t1:opacity-55 peer-checked/t3:z-2 peer-checked/t3:translate-y-7 peer-checked/t3:scale-[0.88] peer-checked/t3:opacity-30 motion-reduce:transition-none">
<blockquote class="m-0 font-stk30-serif text-xl italic leading-snug text-stk30-ink">“Beautiful, fast, and genuinely a joy to use every single day.”</blockquote>
<figcaption class="flex items-center gap-3">
<span class="block size-11 shrink-0 rounded-full bg-linear-[135deg,var(--color-stk30-av2a),var(--color-stk30-av2b)]" aria-hidden="true"></span>
<span><b class="block text-[0.9rem] font-semibold text-stk30-name">Theo Marsh</b><span class="text-[0.78rem] text-stk30-meta">Founder, Northbeam</span></span>
</figcaption>
</figure>
<figure class="col-span-5 col-start-1 row-start-1 flex h-75 w-full flex-col justify-between rounded-[22px] border border-stk30-line bg-stk30-card p-7.5 opacity-0 shadow-[0_18px_40px_rgba(120,80,50,0.18)] translate-y-7 scale-[0.88] z-1 transition-[transform,opacity] duration-600 ease-stk30 peer-checked/t3:z-4 peer-checked/t3:translate-y-0 peer-checked/t3:scale-100 peer-checked/t3:opacity-100 peer-checked/t2:z-3 peer-checked/t2:translate-y-3.5 peer-checked/t2:scale-[0.94] peer-checked/t2:opacity-55 peer-checked/t1:z-2 peer-checked/t1:translate-y-7 peer-checked/t1:scale-[0.88] peer-checked/t1:opacity-30 motion-reduce:transition-none">
<blockquote class="m-0 font-stk30-serif text-xl italic leading-snug text-stk30-ink">“The support alone is worth it. Best decision we made this year.”</blockquote>
<figcaption class="flex items-center gap-3">
<span class="block size-11 shrink-0 rounded-full bg-linear-[135deg,var(--color-stk30-av3a),var(--color-stk30-av3b)]" aria-hidden="true"></span>
<span><b class="block text-[0.9rem] font-semibold text-stk30-name">Lena Ortiz</b><span class="text-[0.78rem] text-stk30-meta">CTO, Brightside</span></span>
</figcaption>
</figure>
<label class="col-start-2 row-start-2 grid size-11 cursor-pointer place-items-center rounded-full before:block before:size-2.75 before:rounded-full before:bg-stk30-dot before:transition-[background-color,transform] before:duration-300 hover:before:bg-stk30-dothover peer-checked/t1:before:scale-130 peer-checked/t1:before:bg-stk30-active peer-focus-visible/t1:outline-3 peer-focus-visible/t1:outline-stk30-active" for="stk-30-t1">
<span class="sr-only">Show testimonial 1</span>
</label>
<label class="col-start-3 row-start-2 grid size-11 cursor-pointer place-items-center rounded-full before:block before:size-2.75 before:rounded-full before:bg-stk30-dot before:transition-[background-color,transform] before:duration-300 hover:before:bg-stk30-dothover peer-checked/t2:before:scale-130 peer-checked/t2:before:bg-stk30-active peer-focus-visible/t2:outline-3 peer-focus-visible/t2:outline-stk30-active" for="stk-30-t2">
<span class="sr-only">Show testimonial 2</span>
</label>
<label class="col-start-4 row-start-2 grid size-11 cursor-pointer place-items-center rounded-full before:block before:size-2.75 before:rounded-full before:bg-stk30-dot before:transition-[background-color,transform] before:duration-300 hover:before:bg-stk30-dothover peer-checked/t3:before:scale-130 peer-checked/t3:before:bg-stk30-active peer-focus-visible/t3:outline-3 peer-focus-visible/t3:outline-stk30-active" for="stk-30-t3">
<span class="sr-only">Show testimonial 3</span>
</label>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-stk30-p1: #fef6ee;
--color-stk30-p1: oklch(0.97 0.016 75);
--color-stk30-p2: #f4e4d4;
--color-stk30-p2: oklch(0.92 0.030 70);
--color-stk30-card: #fffdfa;
--color-stk30-card: oklch(0.99 0.006 80);
--color-stk30-line: #f0e2d2;
--color-stk30-line: oklch(0.93 0.020 75);
--color-stk30-ink: #3a2e24;
--color-stk30-ink: oklch(0.33 0.022 60);
--color-stk30-name: #2a201a;
--color-stk30-name: oklch(0.26 0.020 55);
--color-stk30-meta: #9b7d5f;
--color-stk30-meta: oklch(0.62 0.045 65);
--color-stk30-dot: #d8c3aa;
--color-stk30-dot: oklch(0.83 0.035 75);
--color-stk30-dothover: #c9a98a;
--color-stk30-dothover: oklch(0.76 0.050 70);
--color-stk30-active: #b8553f;
--color-stk30-active: oklch(0.55 0.145 35);
--color-stk30-av1a: #ff9a3c;
--color-stk30-av1b: #ff5e62;
--color-stk30-av2a: #43cea2;
--color-stk30-av2b: #185a9d;
--color-stk30-av3a: #c471f5;
--color-stk30-av3b: #7f53ac;
--font-stk30-sans: 'Geist', system-ui, sans-serif;
--font-stk30-serif: 'Newsreader', Georgia, serif;
--ease-stk30: cubic-bezier(0.3, 0.9, 0.3, 1);
}
```How this works
The mapping is cyclic, which is why named peers earn their keep. Demos 08 and 18 avoided positional selectors by nesting each radio with the one element it controls. That cannot work here: every quote needs a different pose for each of the three radios. Naming the peers makes that direct:
<input class="peer/t1 …" type="radio" name="stk-30" checked>
<input class="peer/t2 …" type="radio" name="stk-30">
<input class="peer/t3 …" type="radio" name="stk-30">
<!-- quote 2 -->
class="peer-checked/t2:z-4 peer-checked/t2:translate-y-0 peer-checked/t2:scale-100 peer-checked/t2:opacity-100
peer-checked/t1:z-3 peer-checked/t1:translate-y-3.5 peer-checked/t1:scale-[0.94] peer-checked/t1:opacity-55
peer-checked/t3:z-2 peer-checked/t3:translate-y-7 peer-checked/t3:scale-[0.88] peer-checked/t3:opacity-30"Nine grouped selectors in the CSS become three variant sets typed on the three elements they describe. Change the rotation and you edit the card, not a selector list.
Hidden, not removed. The radios are absolutely positioned at zero size with opacity-0, so they keep their place in the tab order and the group still answers arrow keys. display:none would take the whole widget off the keyboard.
Testimonials are a legal surface, not just a design one. The FTC's Endorsements Rule requires substantiation and disclosure of material connections. Fabricated quotes in a shipped marketing page are a compliance risk, not a placeholder problem — swap these for real, attributable ones.
Make it yours
- Add a fourth testimonial by adding a radio with
peer/t4, a quote, a dot, and one more variant set per quote. - Change the peek offset with the second- and third-place
translate-yvalues. - Rebrand through the
@themepaper and ink tokens; the dots read the same accent as the active state. - Add star ratings or company logos inside each quote — the card is ordinary flex markup.
- Auto-advance with a tiny
setIntervalthat checks the next radio, keeping the radios as the source of truth.
Gotchas — read before shipping
- Named peers are the tool for many-to-many mappings. Nesting only works when one input controls one subtree, which is not the case in a cyclic stack.
- Hide the radios with
opacity-0and zero size, neverdisplay:none, or you lose focus and arrow-key cycling. - Every
<label>dot needs a matchingfor/idpair; that pairing is also what assistive technology announces. - Testimonials must be honest and substantiated (FTC 16 CFR § 255), with material connections disclosed. This is a legal requirement, not a nicety.
- Keep the deck to five or six quotes; both the class lists and the number of variant sets grow with the square of the count.
- Give the dots a real hit area — the visible dot is 11px, so the label needs padding to reach a usable target.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
Radio :checked with sibling combinators is universal (Chrome 45+, Safari 9+, Firefox 16+); named peers are a Tailwind feature, so the floor is v4's own generated sheet.