← Visual Library / Carousels

Card stack

Cards stacked with subtle rotation. The top card is active; the rest peek out behind. Luxury feel for hand-picked collections.

Carousels Built JS-free
Use when — you have a small curated set (4–6) and want to make each one feel like a "card hand" — premium, hand-picked. Good for featured luxury properties, hero testimonials, founder picks. Bad for 20+ items (use scroll-snap instead).

Live demo

FeaturedVilla Cala VadellaIbiza · 5 bed · 580 m²
FeaturedCasa Sant JosepIbiza · 4 bed · 420 m²
FeaturedFinca Es CubellsIbiza · 6 bed · 720 m²
FeaturedApartamento MarinaIbiza · 3 bed · 220 m²

How it works

Each card is absolutely positioned over the others. The "active" card has transform: translateY(0) rotate(0), the others get progressively higher Y offsets and small rotations to look like they're stacked behind. Radio inputs hold which card is on top; :has() on the parent watches the radios and re-applies the transform classes to whichever cards should be in each position.

The code (HTML)

<div class="stack">
  <input type="radio" name="s" id="s1" checked>
  <input type="radio" name="s" id="s2">
  ...
  <div class="card c1">...</div>
  <div class="card c2">...</div>
</div>
<div class="stack-nav">
  <label for="s1">1</label>
  <label for="s2">2</label>
</div>

The code (CSS — the key bits)

.stack .card { position: absolute; inset: 0; transition: transform 0.5s; }
.stack .c1 { transform: translateY(0); z-index: 4; }
.stack .c2 { transform: translateY(12px) rotate(2deg); z-index: 3; opacity: 0.92; }
.stack:has(#s2:checked) .c2 { transform: translateY(0); z-index: 4; opacity: 1; }
.stack:has(#s2:checked) .c1 { transform: translateY(36px) rotate(3deg); z-index: 1; opacity: 0.7; }

Variants worth considering

Add swipe gestures — for a true "Tinder card" feel, you'd need ~30 lines of JS to handle pointer events. Pure-CSS click-only is honest and works everywhere.

Fan instead of stack — change translateY to translateX with bigger rotations (e.g. 8°, -8°). Each card peeks from a different angle like cards in a hand. Strong "curated selection" feel.

Auto-cycle — pair with a CSS @keyframes animation on the input checked state if you want it to advance automatically. Use cautiously — auto-motion can be disorienting on phones.

Compatibility: :has() needs Safari 15.4+, Chrome 105+, Firefox 121+.