36 CSS Stacked Cards09 / 36
Swipeable / Click-to-Front Stacked Cards
A deck where the front card is the interface: click it and it slides laterally out of bounds, drops to the lowest layer, and glides back into place at the rear — cycling the next card forward. A tactile way to browse a small, ordered set.
Published Updated
The code
<section class="scd-09" aria-label="Click-to-front card deck">
<div class="scd-09__deck" id="scd-09-deck">
<button class="scd-09__card" style="--c:oklch(0.63 0.17 20)"><span>Ticket #4821</span><b>Payment retry loop</b><i>High · Billing</i></button>
<button class="scd-09__card" style="--c:oklch(0.66 0.15 150)"><span>Ticket #4822</span><b>Slow search on mobile</b><i>Medium · Web</i></button>
<button class="scd-09__card" style="--c:oklch(0.64 0.16 250)"><span>Ticket #4823</span><b>Export to CSV missing</b><i>Low · Reports</i></button>
<button class="scd-09__card" style="--c:oklch(0.64 0.16 300)"><span>Ticket #4824</span><b>SSO redirect fails</b><i>High · Auth</i></button>
</div>
<div class="scd-09__foot"><button class="scd-09__cyc" id="scd-09-next">Next ticket ↻</button><p id="scd-09-live" aria-live="polite" class="scd-09__sr"></p></div>
</section><section class="scd-09" aria-label="Click-to-front card deck">
<div class="scd-09__deck" id="scd-09-deck">
<button class="scd-09__card" style="--c:oklch(0.63 0.17 20)"><span>Ticket #4821</span><b>Payment retry loop</b><i>High · Billing</i></button>
<button class="scd-09__card" style="--c:oklch(0.66 0.15 150)"><span>Ticket #4822</span><b>Slow search on mobile</b><i>Medium · Web</i></button>
<button class="scd-09__card" style="--c:oklch(0.64 0.16 250)"><span>Ticket #4823</span><b>Export to CSV missing</b><i>Low · Reports</i></button>
<button class="scd-09__card" style="--c:oklch(0.64 0.16 300)"><span>Ticket #4824</span><b>SSO redirect fails</b><i>High · Auth</i></button>
</div>
<div class="scd-09__foot"><button class="scd-09__cyc" id="scd-09-next">Next ticket ↻</button><p id="scd-09-live" aria-live="polite" class="scd-09__sr"></p></div>
</section>.scd-09,
.scd-09 *,
.scd-09 *::before,
.scd-09 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.scd-09 {
font-family: 'Segoe UI',system-ui,sans-serif;
width: 100%;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 28px;
padding: 50px 20px;
background: #101219;
}
.scd-09__deck {
position: relative;
width: min(320px,86vw);
height: 220px;
}
.scd-09__card {
position: absolute;
inset: 0;
text-align: left;
border: none;
cursor: pointer;
padding: 24px;
border-radius: 20px;
color: #fff;
background: var(--c);
box-shadow: 0 24px 46px -26px rgba(0,0,0,.8);
display: flex;
flex-direction: column;
gap: 8px;
transform: translateY(calc(var(--depth,0) * 16px)) scale(calc(1 - var(--depth,0) * .05));
z-index: calc(10 - var(--depth,0));
transition: transform .5s cubic-bezier(.3,.9,.3,1),opacity .5s;
}
.scd-09__card.scd-09__sending {
transform: translateX(130%) rotate(12deg);
opacity: 0;
transition: transform .45s ease-in,opacity .45s;
}
.scd-09__card span {
font-size: 12px;
font-weight: 700;
letter-spacing: .08em;
opacity: .85;
}
.scd-09__card b {
font-size: 20px;
font-weight: 800;
}
.scd-09__card i {
font-style: normal;
font-size: 12px;
opacity: .85;
margin-top: auto;
}
.scd-09__card:focus-visible {
outline: 3px solid #fff;
outline-offset: 3px;
}
.scd-09__cyc {
background: #232838;
color: #fff;
border: 1px solid #333a4e;
padding: 11px 22px;
border-radius: 12px;
font-weight: 700;
font-size: 14px;
cursor: pointer;
transition: background .2s;
}
.scd-09__cyc:hover {
background: #2e3446;
}
.scd-09__cyc:focus-visible {
outline: 3px solid oklch(0.66 0.15 250);
outline-offset: 2px;
}
.scd-09__sr {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip: rect(0 0 0 0);
}
@media (prefers-reduced-motion: reduce) {
.scd-09__card {
transition: transform .2s,opacity .2s;
}
}.scd-09,
.scd-09 *,
.scd-09 *::before,
.scd-09 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.scd-09 {
font-family: 'Segoe UI',system-ui,sans-serif;
width: 100%;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 28px;
padding: 50px 20px;
background: #101219;
}
.scd-09__deck {
position: relative;
width: min(320px,86vw);
height: 220px;
}
.scd-09__card {
position: absolute;
inset: 0;
text-align: left;
border: none;
cursor: pointer;
padding: 24px;
border-radius: 20px;
color: #fff;
background: var(--c);
box-shadow: 0 24px 46px -26px rgba(0,0,0,.8);
display: flex;
flex-direction: column;
gap: 8px;
transform: translateY(calc(var(--depth,0) * 16px)) scale(calc(1 - var(--depth,0) * .05));
z-index: calc(10 - var(--depth,0));
transition: transform .5s cubic-bezier(.3,.9,.3,1),opacity .5s;
}
.scd-09__card.scd-09__sending {
transform: translateX(130%) rotate(12deg);
opacity: 0;
transition: transform .45s ease-in,opacity .45s;
}
.scd-09__card span {
font-size: 12px;
font-weight: 700;
letter-spacing: .08em;
opacity: .85;
}
.scd-09__card b {
font-size: 20px;
font-weight: 800;
}
.scd-09__card i {
font-style: normal;
font-size: 12px;
opacity: .85;
margin-top: auto;
}
.scd-09__card:focus-visible {
outline: 3px solid #fff;
outline-offset: 3px;
}
.scd-09__cyc {
background: #232838;
color: #fff;
border: 1px solid #333a4e;
padding: 11px 22px;
border-radius: 12px;
font-weight: 700;
font-size: 14px;
cursor: pointer;
transition: background .2s;
}
.scd-09__cyc:hover {
background: #2e3446;
}
.scd-09__cyc:focus-visible {
outline: 3px solid oklch(0.66 0.15 250);
outline-offset: 2px;
}
.scd-09__sr {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip: rect(0 0 0 0);
}
@media (prefers-reduced-motion: reduce) {
.scd-09__card {
transition: transform .2s,opacity .2s;
}
}(() => {
const root = document.querySelector('.scd-09');
const deck = root.querySelector('#scd-09-deck');
const live = root.querySelector('#scd-09-live');
const paint = () => [...deck.children].forEach((c,i) => c.style.setProperty('--depth', i));
function cycle(){
const front = deck.firstElementChild; if(!front) return;
front.classList.add('scd-09__sending');
front.addEventListener('transitionend', () => {
front.classList.remove('scd-09__sending');
deck.appendChild(front); paint();
const nf = deck.firstElementChild.querySelector('b');
if(nf) live.textContent = 'Now showing ' + nf.textContent;
}, {once:true});
}
deck.addEventListener('click', e => { if(e.target.closest('.scd-09__card') === deck.firstElementChild) cycle(); });
root.querySelector('#scd-09-next').addEventListener('click', cycle);
paint();
})();(() => {
const root = document.querySelector('.scd-09');
const deck = root.querySelector('#scd-09-deck');
const live = root.querySelector('#scd-09-live');
const paint = () => [...deck.children].forEach((c,i) => c.style.setProperty('--depth', i));
function cycle(){
const front = deck.firstElementChild; if(!front) return;
front.classList.add('scd-09__sending');
front.addEventListener('transitionend', () => {
front.classList.remove('scd-09__sending');
deck.appendChild(front); paint();
const nf = deck.firstElementChild.querySelector('b');
if(nf) live.textContent = 'Now showing ' + nf.textContent;
}, {once:true});
}
deck.addEventListener('click', e => { if(e.target.closest('.scd-09__card') === deck.firstElementChild) cycle(); });
root.querySelector('#scd-09-next').addEventListener('click', cycle);
paint();
})();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: Swipeable / Click-to-Front Stacked Cards
Source: https://codefronts.com/components/css-stacked-cards/swipeable-click-to-front-stacked-cards/
A deck where the front card is the interface: click it and it slides laterally out of bounds, drops to the lowest layer, and glides back into place at the rear — cycling the next card forward. A tactile way to browse a small, ordered set.
## HTML
```html
<section class="scd-09" aria-label="Click-to-front card deck">
<div class="scd-09__deck" id="scd-09-deck">
<button class="scd-09__card" style="--c:oklch(0.63 0.17 20)"><span>Ticket #4821</span><b>Payment retry loop</b><i>High · Billing</i></button>
<button class="scd-09__card" style="--c:oklch(0.66 0.15 150)"><span>Ticket #4822</span><b>Slow search on mobile</b><i>Medium · Web</i></button>
<button class="scd-09__card" style="--c:oklch(0.64 0.16 250)"><span>Ticket #4823</span><b>Export to CSV missing</b><i>Low · Reports</i></button>
<button class="scd-09__card" style="--c:oklch(0.64 0.16 300)"><span>Ticket #4824</span><b>SSO redirect fails</b><i>High · Auth</i></button>
</div>
<div class="scd-09__foot"><button class="scd-09__cyc" id="scd-09-next">Next ticket ↻</button><p id="scd-09-live" aria-live="polite" class="scd-09__sr"></p></div>
</section>
```
## CSS
```css
.scd-09,
.scd-09 *,
.scd-09 *::before,
.scd-09 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.scd-09 {
font-family: 'Segoe UI',system-ui,sans-serif;
width: 100%;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 28px;
padding: 50px 20px;
background: #101219;
}
.scd-09__deck {
position: relative;
width: min(320px,86vw);
height: 220px;
}
.scd-09__card {
position: absolute;
inset: 0;
text-align: left;
border: none;
cursor: pointer;
padding: 24px;
border-radius: 20px;
color: #fff;
background: var(--c);
box-shadow: 0 24px 46px -26px rgba(0,0,0,.8);
display: flex;
flex-direction: column;
gap: 8px;
transform: translateY(calc(var(--depth,0) * 16px)) scale(calc(1 - var(--depth,0) * .05));
z-index: calc(10 - var(--depth,0));
transition: transform .5s cubic-bezier(.3,.9,.3,1),opacity .5s;
}
.scd-09__card.scd-09__sending {
transform: translateX(130%) rotate(12deg);
opacity: 0;
transition: transform .45s ease-in,opacity .45s;
}
.scd-09__card span {
font-size: 12px;
font-weight: 700;
letter-spacing: .08em;
opacity: .85;
}
.scd-09__card b {
font-size: 20px;
font-weight: 800;
}
.scd-09__card i {
font-style: normal;
font-size: 12px;
opacity: .85;
margin-top: auto;
}
.scd-09__card:focus-visible {
outline: 3px solid #fff;
outline-offset: 3px;
}
.scd-09__cyc {
background: #232838;
color: #fff;
border: 1px solid #333a4e;
padding: 11px 22px;
border-radius: 12px;
font-weight: 700;
font-size: 14px;
cursor: pointer;
transition: background .2s;
}
.scd-09__cyc:hover {
background: #2e3446;
}
.scd-09__cyc:focus-visible {
outline: 3px solid oklch(0.66 0.15 250);
outline-offset: 2px;
}
.scd-09__sr {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip: rect(0 0 0 0);
}
@media (prefers-reduced-motion: reduce) {
.scd-09__card {
transition: transform .2s,opacity .2s;
}
}
```
## JavaScript
```js
(() => {
const root = document.querySelector('.scd-09');
const deck = root.querySelector('#scd-09-deck');
const live = root.querySelector('#scd-09-live');
const paint = () => [...deck.children].forEach((c,i) => c.style.setProperty('--depth', i));
function cycle(){
const front = deck.firstElementChild; if(!front) return;
front.classList.add('scd-09__sending');
front.addEventListener('transitionend', () => {
front.classList.remove('scd-09__sending');
deck.appendChild(front); paint();
const nf = deck.firstElementChild.querySelector('b');
if(nf) live.textContent = 'Now showing ' + nf.textContent;
}, {once:true});
}
deck.addEventListener('click', e => { if(e.target.closest('.scd-09__card') === deck.firstElementChild) cycle(); });
root.querySelector('#scd-09-next').addEventListener('click', cycle);
paint();
})();
```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: Swipeable / Click-to-Front Stacked Cards
Source: https://codefronts.com/components/css-stacked-cards/swipeable-click-to-front-stacked-cards/
A deck where the front card is the interface: click it and it slides laterally out of bounds, drops to the lowest layer, and glides back into place at the rear — cycling the next card forward. A tactile way to browse a small, ordered set.
## HTML
```html
<section class="scd-09" aria-label="Click-to-front card deck">
<div class="scd-09__deck" id="scd-09-deck">
<button class="scd-09__card" style="--c:oklch(0.63 0.17 20)"><span>Ticket #4821</span><b>Payment retry loop</b><i>High · Billing</i></button>
<button class="scd-09__card" style="--c:oklch(0.66 0.15 150)"><span>Ticket #4822</span><b>Slow search on mobile</b><i>Medium · Web</i></button>
<button class="scd-09__card" style="--c:oklch(0.64 0.16 250)"><span>Ticket #4823</span><b>Export to CSV missing</b><i>Low · Reports</i></button>
<button class="scd-09__card" style="--c:oklch(0.64 0.16 300)"><span>Ticket #4824</span><b>SSO redirect fails</b><i>High · Auth</i></button>
</div>
<div class="scd-09__foot"><button class="scd-09__cyc" id="scd-09-next">Next ticket ↻</button><p id="scd-09-live" aria-live="polite" class="scd-09__sr"></p></div>
</section>
```
## CSS
```css
.scd-09,
.scd-09 *,
.scd-09 *::before,
.scd-09 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.scd-09 {
font-family: 'Segoe UI',system-ui,sans-serif;
width: 100%;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 28px;
padding: 50px 20px;
background: #101219;
}
.scd-09__deck {
position: relative;
width: min(320px,86vw);
height: 220px;
}
.scd-09__card {
position: absolute;
inset: 0;
text-align: left;
border: none;
cursor: pointer;
padding: 24px;
border-radius: 20px;
color: #fff;
background: var(--c);
box-shadow: 0 24px 46px -26px rgba(0,0,0,.8);
display: flex;
flex-direction: column;
gap: 8px;
transform: translateY(calc(var(--depth,0) * 16px)) scale(calc(1 - var(--depth,0) * .05));
z-index: calc(10 - var(--depth,0));
transition: transform .5s cubic-bezier(.3,.9,.3,1),opacity .5s;
}
.scd-09__card.scd-09__sending {
transform: translateX(130%) rotate(12deg);
opacity: 0;
transition: transform .45s ease-in,opacity .45s;
}
.scd-09__card span {
font-size: 12px;
font-weight: 700;
letter-spacing: .08em;
opacity: .85;
}
.scd-09__card b {
font-size: 20px;
font-weight: 800;
}
.scd-09__card i {
font-style: normal;
font-size: 12px;
opacity: .85;
margin-top: auto;
}
.scd-09__card:focus-visible {
outline: 3px solid #fff;
outline-offset: 3px;
}
.scd-09__cyc {
background: #232838;
color: #fff;
border: 1px solid #333a4e;
padding: 11px 22px;
border-radius: 12px;
font-weight: 700;
font-size: 14px;
cursor: pointer;
transition: background .2s;
}
.scd-09__cyc:hover {
background: #2e3446;
}
.scd-09__cyc:focus-visible {
outline: 3px solid oklch(0.66 0.15 250);
outline-offset: 2px;
}
.scd-09__sr {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip: rect(0 0 0 0);
}
@media (prefers-reduced-motion: reduce) {
.scd-09__card {
transition: transform .2s,opacity .2s;
}
}
```
## JavaScript
```js
(() => {
const root = document.querySelector('.scd-09');
const deck = root.querySelector('#scd-09-deck');
const live = root.querySelector('#scd-09-live');
const paint = () => [...deck.children].forEach((c,i) => c.style.setProperty('--depth', i));
function cycle(){
const front = deck.firstElementChild; if(!front) return;
front.classList.add('scd-09__sending');
front.addEventListener('transitionend', () => {
front.classList.remove('scd-09__sending');
deck.appendChild(front); paint();
const nf = deck.firstElementChild.querySelector('b');
if(nf) live.textContent = 'Now showing ' + nf.textContent;
}, {once:true});
}
deck.addEventListener('click', e => { if(e.target.closest('.scd-09__card') === deck.firstElementChild) cycle(); });
root.querySelector('#scd-09-next').addEventListener('click', cycle);
paint();
})();
```How this works
Cards are absolutely stacked; their depth is data, not markup order. A tiny controller keeps an array of card elements and, on click of the front card (or the Next button), applies a .sending class that flings it sideways, then re-appends it to the end and re-paints every card's --depth custom property. CSS maps --depth to translateY+scale+z-index, so the reshuffle animates cleanly.
Buttons and Enter/Space on the focused front card trigger the same cycle, and aria-live reads the new front card's title.
Make it yours
- Change the fling direction/curve in the
.sendingtransform. - Set stack depth via the
--depth→ transform mapping. - Add a Prev action by re-inserting the last card at the front.
- Recolour cards through their inline accent.
Gotchas — read before shipping
- Drive depth from a data attribute, not DOM order, so re-appending doesn't fight your CSS.
- Wait for
transitionendbefore re-appending, or the card snaps. - Keep a button + key handler so it's not click-only (a11y).
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 15.4+ | 113+ | 111+ |
Floor set by oklch() as this demo is written. The core technique itself goes back further — Chrome 49+.
CSS custom properties + transforms + a few lines of vanilla JS; supported across modern browsers.