Cards & galleries
Flip card
Pure CSSHover, tap or focus to flip. The back face is hidden until it turns toward you.
Four slides on the sides of a box. JS counts steps; CSS turns the box by 90° per step.
Your edited version
rotateY(n × 90deg) translateZ(width / 2).translateZ(-width / 2), so whichever face is in front sits at z = 0 and renders at its true size.rotateY(calc(var(--step) * -90deg)).rotateY(calc(var(--step) * -90deg))unbounded counter = endless rotationtranslateZ(-w/2) keeps the front at z = 0<div class="slider">
<div class="scene">
<div class="box">
<i style="--hue:250">Design</i>
<i style="--hue:290">Build</i>
<i style="--hue:330">Ship</i>
<i style="--hue:10">Repeat</i>
</div>
</div>
<nav>
<button type="button" data-dir="-1" aria-label="Previous">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m15 18-6-6 6-6"/></svg>
</button>
<button type="button" data-dir="1" aria-label="Next">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m9 18 6-6-6-6"/></svg>
</button>
</nav>
</div>
.slider {
display: grid;
justify-items: center;
gap: 50px;
}
.scene {
perspective: 800px;
}
.box {
--w: 280px;
position: relative;
width: var(--w);
height: 170px;
transform-style: preserve-3d;
transform: translateZ(calc(var(--w) / -2)) rotateY(calc(var(--step, 0) * -90deg));
transition: transform 0.7s cubic-bezier(0.3, 1.25, 0.5, 1);
}
.box i {
position: absolute;
inset: 0;
display: grid;
place-items: center;
border-radius: 8px;
color: #fff;
font: 900 2rem system-ui;
background: linear-gradient(135deg, hsl(var(--hue) 85% 62%), hsl(var(--hue) 75% 38%));
backface-visibility: hidden;
transform-style: preserve-3d;
}
/* plugs the see-through notches where the rounded corners of two slides meet */
.box i::before {
content: '';
position: absolute;
inset: 6px;
background: hsl(var(--hue) 75% 34%);
transform: translateZ(-6px);
}
.box i:nth-child(1) { transform: rotateY(0deg) translateZ(calc(var(--w) / 2)); }
.box i:nth-child(2) { transform: rotateY(90deg) translateZ(calc(var(--w) / 2)); }
.box i:nth-child(3) { transform: rotateY(180deg) translateZ(calc(var(--w) / 2)); }
.box i:nth-child(4) { transform: rotateY(270deg) translateZ(calc(var(--w) / 2)); }
nav { display: flex; gap: 8px; }
nav button {
width: 44px;
height: 36px;
border: 1px solid #5a6188;
border-radius: 8px;
display: grid;
place-items: center;
padding: 0;
background: #161a2e;
color: #fff;
cursor: pointer;
}
/* SVG arrows, not text like ‹ ›: a glyph sits on the font's baseline, so it never centres */
nav svg {
width: 18px;
height: 18px;
}
const box = document.querySelector('.box');
let step = 0; // never wraps, so the box keeps turning the same way
document.querySelector('nav').addEventListener('click', (e) => {
const btn = e.target.closest('[data-dir]');
if (!btn) return;
step += Number(btn.dataset.dir);
box.style.setProperty('--step', step);
});
@use '../mixins' as *;
$w: 190px;
.d-boxslider {
display: grid;
grid-template-rows: 1fr auto;
justify-items: center;
width: 100%;
height: 100%;
padding: 10px;
&__view {
display: grid;
place-items: center;
perspective: 700px;
}
&__box {
position: relative;
width: $w;
height: 120px;
transform-style: preserve-3d;
// --step is an ever-growing (or shrinking) integer written by JS.
transform: translateZ($w * -0.5) rotateY(calc(var(--step, 0) * -90deg));
transition: transform 0.7s cubic-bezier(0.3, 1.25, 0.5, 1);
i {
position: absolute;
inset: 0;
display: grid;
place-items: center;
border-radius: 6px;
background: linear-gradient(135deg, hsl(var(--hue) 85% 62%), hsl(var(--hue) 75% 38%));
color: #fff;
font-size: 26px;
font-style: normal;
font-weight: 900;
backface-visibility: hidden;
@include solid-core(5px, hsl(var(--hue) 75% 34%));
@for $i from 1 through 4 {
&:nth-child(#{$i}) {
transform: rotateY(($i - 1) * 90deg) translateZ($w * 0.5);
}
}
}
}
&__nav {
display: flex;
gap: 8px;
button {
width: 34px;
height: 28px;
border: 1px solid var(--border-strong);
border-radius: 8px;
background: var(--surface);
display: grid;
place-items: center;
padding: 0;
cursor: pointer;
&:hover {
border-color: var(--warm);
}
}
}
}