3D Cube Flip Card Using Pure HTML & CSS
These notes explain how to build a 3D cube-style flip card step by step using only HTML and CSS, exactly like a team/people card that shows an image on the front and rotates to reveal content on hover.
This is written for future reference + blog posting (Hashnode) and focuses on why each step exists, not just what to write.
1. The Goal (Mental Model)
We are not making a normal flip card.
We are simulating a 3D cube rotation:
Front face → Image
Side face → Text (name + bio)
On hover → cube rotates 90° on Y-axis
Key idea:
The browser must believe the element has depth.
2. Required HTML Structure (Very Important)
<div class="card">
<div class="card-inner">
<div class="card-front">
<img src="photo.png" />
</div>
<div class="card-side">
<h3>Name</h3>
<p>Description</p>
</div>
</div>
</div>
Why this structure?
| Element | Role |
.card | Camera (perspective) |
.card-inner | Cube (rotates) |
.card-front | Front face |
.card-side | Side face |
Never skip this nesting — 3D depends on it.
3. Card Container = Camera
.card {
aspect-ratio: 1 / 1;
position: relative;
perspective: 1000px;
}
Why aspect-ratio: 1 / 1?
Forces a perfect square
Cube math only works correctly on equal width & height
Why position: relative?
Anchors absolutely positioned faces
Prevents faces from escaping the card
Why perspective?
Enables real 3D depth
Controls how dramatic the rotation looks
Without perspective → everything looks flat.
4. Cube Depth Variable
.card {
--depth: 150px;
}
This represents half the cube thickness.
All faces will be positioned using this value.
5. The Rotating Cube (card-inner)
.card-inner {
width: 100%;
height: 100%;
position: relative;
transform-style: preserve-3d;
transform: translateZ(calc(var(--depth) * -1));
transition: transform 0.6s ease;
}
Line-by-line purpose:
transform-style: preserve-3d
Prevents browser from flattening child elements
Allows faces to exist on Z-axis
Without it → no cube, no depth.
translateZ(-depth)
Pulls the entire cube backward
Balances faces that are pushed forward
This centers the cube in 3D space.
6. Front Face (Image Side)
.card-front {
position: absolute;
inset: 0;
backface-visibility: hidden;
transform: translateZ(var(--depth));
}
What each line does:
position: absolute→ allows stackinginset: 0→ fills the card perfectlybackface-visibility: hidden→ hides mirrored backsidetranslateZ(depth)→ pushes face to the front of cube
This is what the user sees initially.
7. Side Face (Text Side)
.card-side {
position: absolute;
inset: 0;
backface-visibility: hidden;
transform: rotateY(90deg) translateZ(var(--depth));
}
Key idea:
This face starts turned sideways, so it is invisible.
rotateY(90deg)→ faces sidewaystranslateZ(depth)→ places it on cube edge
This is NOT the back — it is the side of the cube.
8. Hover Rotation
.card:hover .card-inner {
transform: translateZ(calc(var(--depth) * -1)) rotateY(-90deg);
}
What happens on hover:
Cube rotates left
Front face moves out of view
Side face rotates into view
Feels like a real cube turn
Why 90deg and not 180deg?
180deg = flip card
90deg = cube rotation
9. Why backface-visibility Is Mandatory
Without it:
Text appears mirrored
Faces overlap visually
Rotation looks broken
With it:
- Only faces pointing toward the viewer are rendered
10. Common Mistakes (and Why They Break)
| Mistake | Result |
No perspective | Flat animation |
No preserve-3d | Faces collapse |
No translateZ(-depth) | Off-center rotation |
| Using 180deg | Wrong animation |
No aspect-ratio | Distorted cube |
11. One-Paragraph Explanation (Interview / Blog)
I create a 3D cube effect by placing two faces in 3D space using
translateZandrotateY, preserving depth withtransform-style: preserve-3d, and rotating the inner wrapper by 90 degrees on hover. The parent element provides perspective, while the cube is centered using a negative Z translation to maintain correct rotation geometry.
12. Final Takeaway
3D CSS is about balance:
Faces go forward
Cube goes backward
Camera stays outside
When all three are correct → the illusion works.