36 CSS Stacked Cards08 / 36

Pure CSSMIT licensed

Dynamic Tabbed Stack Deck

Cards stacked directly on top of one another, each with an offset colour-coded top tab sticking out. Click any visible tab and that card jumps to the absolute front while the rest translate back — a filing-cabinet metaphor built entirely with radios.

Published

Live Demo
Try it

The code

<section class="scd-08" aria-label="Tabbed card deck">
  <div class="scd-08__deck">
    <input type="radio" name="scd-08" id="scd-08-1" class="scd-08__r" checked>
    <input type="radio" name="scd-08" id="scd-08-2" class="scd-08__r">
    <input type="radio" name="scd-08" id="scd-08-3" class="scd-08__r">
    <input type="radio" name="scd-08" id="scd-08-4" class="scd-08__r">
    <label class="scd-08__tab" for="scd-08-1" style="--c:oklch(0.65 0.17 25);--l:0">Overview</label>
    <label class="scd-08__tab" for="scd-08-2" style="--c:oklch(0.7 0.16 145);--l:1">Pricing</label>
    <label class="scd-08__tab" for="scd-08-3" style="--c:oklch(0.68 0.15 235);--l:2">Docs</label>
    <label class="scd-08__tab" for="scd-08-4" style="--c:oklch(0.68 0.16 300);--l:3">Support</label>
    <article class="scd-08__card" style="--c:oklch(0.65 0.17 25)"><h3>Overview</h3><p>One deck, four surfaces. Click a tab to bring its card to the very front.</p></article>
    <article class="scd-08__card" style="--c:oklch(0.7 0.16 145)"><h3>Pricing</h3><p>Flat monthly tiers, no seats, cancel anytime. Start free, upgrade when it pays off.</p></article>
    <article class="scd-08__card" style="--c:oklch(0.68 0.15 235)"><h3>Docs</h3><p>Copy-paste snippets, live playgrounds and a searchable API. Zero setup to try.</p></article>
    <article class="scd-08__card" style="--c:oklch(0.68 0.16 300)"><h3>Support</h3><p>Real humans, weekday chat, and a status page that actually stays current.</p></article>
  </div>
</section>
.scd-08,
.scd-08 *,
.scd-08 *::before,
.scd-08 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.scd-08 {
  font-family: 'Segoe UI',system-ui,sans-serif;
  width: 100%;
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 60px 20px;
  background: #20222c;
}

.scd-08__deck {
  position: relative;
  width: min(400px,100%);
  height: 300px;
  padding-top: 44px;
}

.scd-08__r {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
}

.scd-08__tab {
  position: absolute;
  top: 0;
  left: calc(var(--l) * 25%);
  width: 24%;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 13px;
  font-weight: 700;
  color: #fff;
  background: var(--c);
  border-radius: 12px 12px 0 0;
  cursor: pointer;
  z-index: 5;
  filter: brightness(.8);
  transition: filter .25s,transform .25s;
}

.scd-08__tab:hover {
  filter: brightness(.95);
}

.scd-08__card {
  position: absolute;
  left: 0;
  right: 0;
  top: 40px;
  height: 250px;
  padding: 28px;
  border-radius: 0 16px 16px 16px;
  color: #fff;
  background: var(--c);
  box-shadow: 0 26px 50px -28px rgba(0,0,0,.7);
  transform: translateY(24px) scale(.94);
  transition: transform .45s cubic-bezier(.3,.9,.3,1);
  z-index: 1;
}

.scd-08__card h3 {
  font-size: 24px;
  font-weight: 800;
  margin-bottom: 12px;
  letter-spacing: -.02em;
}

.scd-08__card p {
  font-size: 15px;
  line-height: 1.6;
  max-width: 34ch;
  opacity: .95;
}

.scd-08__r:nth-of-type(1):checked ~ .scd-08__tab:nth-of-type(1),
.scd-08__r:nth-of-type(2):checked ~ .scd-08__tab:nth-of-type(2),
.scd-08__r:nth-of-type(3):checked ~ .scd-08__tab:nth-of-type(3),
.scd-08__r:nth-of-type(4):checked ~ .scd-08__tab:nth-of-type(4) {
  filter: brightness(1);
  transform: translateY(-4px);
}

.scd-08__r:nth-of-type(1):checked ~ .scd-08__card:nth-of-type(1),
.scd-08__r:nth-of-type(2):checked ~ .scd-08__card:nth-of-type(2),
.scd-08__r:nth-of-type(3):checked ~ .scd-08__card:nth-of-type(3),
.scd-08__r:nth-of-type(4):checked ~ .scd-08__card:nth-of-type(4) {
  transform: translateY(0) scale(1);
  z-index: 4;
}

.scd-08__r:focus-visible ~ .scd-08__tab {
  outline: 3px solid #fff;
  outline-offset: 2px;
}

.scd-08__r:nth-of-type(1):focus-visible ~ .scd-08__tab:nth-of-type(1),
.scd-08__r:nth-of-type(2):focus-visible ~ .scd-08__tab:nth-of-type(2),
.scd-08__r:nth-of-type(3):focus-visible ~ .scd-08__tab:nth-of-type(3),
.scd-08__r:nth-of-type(4):focus-visible ~ .scd-08__tab:nth-of-type(4) {
  outline: 3px solid #fff;
  outline-offset: 3px;
}

@media (prefers-reduced-motion: reduce) {
  .scd-08__tab,
    .scd-08__card {
    transition: none;
  }
}
/* No JavaScript — a radio :checked state machine pulls the chosen card to the front z-index and nudges the rest back to reveal their tabs. */
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 Stacked Card 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: Dynamic Tabbed Stack Deck
Source: https://codefronts.com/components/css-stacked-cards/dynamic-tabbed-stack-deck/

Cards stacked directly on top of one another, each with an offset colour-coded top tab sticking out. Click any visible tab and that card jumps to the absolute front while the rest translate back — a filing-cabinet metaphor built entirely with radios.
## HTML
```html
<section class="scd-08" aria-label="Tabbed card deck">
  <div class="scd-08__deck">
    <input type="radio" name="scd-08" id="scd-08-1" class="scd-08__r" checked>
    <input type="radio" name="scd-08" id="scd-08-2" class="scd-08__r">
    <input type="radio" name="scd-08" id="scd-08-3" class="scd-08__r">
    <input type="radio" name="scd-08" id="scd-08-4" class="scd-08__r">
    <label class="scd-08__tab" for="scd-08-1" style="--c:oklch(0.65 0.17 25);--l:0">Overview</label>
    <label class="scd-08__tab" for="scd-08-2" style="--c:oklch(0.7 0.16 145);--l:1">Pricing</label>
    <label class="scd-08__tab" for="scd-08-3" style="--c:oklch(0.68 0.15 235);--l:2">Docs</label>
    <label class="scd-08__tab" for="scd-08-4" style="--c:oklch(0.68 0.16 300);--l:3">Support</label>
    <article class="scd-08__card" style="--c:oklch(0.65 0.17 25)"><h3>Overview</h3><p>One deck, four surfaces. Click a tab to bring its card to the very front.</p></article>
    <article class="scd-08__card" style="--c:oklch(0.7 0.16 145)"><h3>Pricing</h3><p>Flat monthly tiers, no seats, cancel anytime. Start free, upgrade when it pays off.</p></article>
    <article class="scd-08__card" style="--c:oklch(0.68 0.15 235)"><h3>Docs</h3><p>Copy-paste snippets, live playgrounds and a searchable API. Zero setup to try.</p></article>
    <article class="scd-08__card" style="--c:oklch(0.68 0.16 300)"><h3>Support</h3><p>Real humans, weekday chat, and a status page that actually stays current.</p></article>
  </div>
</section>
```
## CSS
```css
.scd-08,
.scd-08 *,
.scd-08 *::before,
.scd-08 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.scd-08 {
  font-family: 'Segoe UI',system-ui,sans-serif;
  width: 100%;
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 60px 20px;
  background: #20222c;
}

.scd-08__deck {
  position: relative;
  width: min(400px,100%);
  height: 300px;
  padding-top: 44px;
}

.scd-08__r {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
}

.scd-08__tab {
  position: absolute;
  top: 0;
  left: calc(var(--l) * 25%);
  width: 24%;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 13px;
  font-weight: 700;
  color: #fff;
  background: var(--c);
  border-radius: 12px 12px 0 0;
  cursor: pointer;
  z-index: 5;
  filter: brightness(.8);
  transition: filter .25s,transform .25s;
}

.scd-08__tab:hover {
  filter: brightness(.95);
}

.scd-08__card {
  position: absolute;
  left: 0;
  right: 0;
  top: 40px;
  height: 250px;
  padding: 28px;
  border-radius: 0 16px 16px 16px;
  color: #fff;
  background: var(--c);
  box-shadow: 0 26px 50px -28px rgba(0,0,0,.7);
  transform: translateY(24px) scale(.94);
  transition: transform .45s cubic-bezier(.3,.9,.3,1);
  z-index: 1;
}

.scd-08__card h3 {
  font-size: 24px;
  font-weight: 800;
  margin-bottom: 12px;
  letter-spacing: -.02em;
}

.scd-08__card p {
  font-size: 15px;
  line-height: 1.6;
  max-width: 34ch;
  opacity: .95;
}

.scd-08__r:nth-of-type(1):checked ~ .scd-08__tab:nth-of-type(1),
.scd-08__r:nth-of-type(2):checked ~ .scd-08__tab:nth-of-type(2),
.scd-08__r:nth-of-type(3):checked ~ .scd-08__tab:nth-of-type(3),
.scd-08__r:nth-of-type(4):checked ~ .scd-08__tab:nth-of-type(4) {
  filter: brightness(1);
  transform: translateY(-4px);
}

.scd-08__r:nth-of-type(1):checked ~ .scd-08__card:nth-of-type(1),
.scd-08__r:nth-of-type(2):checked ~ .scd-08__card:nth-of-type(2),
.scd-08__r:nth-of-type(3):checked ~ .scd-08__card:nth-of-type(3),
.scd-08__r:nth-of-type(4):checked ~ .scd-08__card:nth-of-type(4) {
  transform: translateY(0) scale(1);
  z-index: 4;
}

.scd-08__r:focus-visible ~ .scd-08__tab {
  outline: 3px solid #fff;
  outline-offset: 2px;
}

.scd-08__r:nth-of-type(1):focus-visible ~ .scd-08__tab:nth-of-type(1),
.scd-08__r:nth-of-type(2):focus-visible ~ .scd-08__tab:nth-of-type(2),
.scd-08__r:nth-of-type(3):focus-visible ~ .scd-08__tab:nth-of-type(3),
.scd-08__r:nth-of-type(4):focus-visible ~ .scd-08__tab:nth-of-type(4) {
  outline: 3px solid #fff;
  outline-offset: 3px;
}

@media (prefers-reduced-motion: reduce) {
  .scd-08__tab,
    .scd-08__card {
    transition: none;
  }
}
```

## JavaScript
```js
/* No JavaScript — a radio :checked state machine pulls the chosen card to the front z-index and nudges the rest back to reveal their tabs. */
```

How this works

A shared radio group backs each card; the card's clickable tab is a <label>. Tabs are spread horizontally so they never overlap. On :checked, the matching card's z-index and transform pull it fully forward while the sibling chain nudges the others down and back, revealing their tabs. It's a pure CSS state machine — the depth hierarchy is data, not script.

Radios keep native arrow-key switching between tabs, :focus-visible rings the active tab, and each card's colour matches its tab so the mental model stays obvious.

Make it yours

  • Add a card by adding one radio + tab + panel and one colour.
  • Change the resting fan by editing the un-checked translateY.
  • Reposition tabs via their left offsets.
  • Recolour each card/tab pair through its inline --c.

Gotchas — read before shipping

  • Space tabs so all stay clickable even when cards overlap.
  • Keep radios focusable (sr-only, not display:none) for keyboard tab-switching.
  • Ensure the front card fully covers the others' bodies but never their tabs.

Browser support

ChromeSafariFirefoxEdge
111+15.4+113+111+

Floor set by oklch() as this demo is written. The core technique itself goes back further — Chrome 49+.

Radio :checked sibling combinators + transforms; universal in modern browsers.

Techniques used in this demo

Search CodeFronts

Loading…