CSS 3D Transforms
Optional Advanced just tuned pointer, caret, and native chrome. This last optional session is a different toolkit: 3D transforms. You give a card a vanishing point and tilt it in depth. You are not remaking the 2D translate / scale hover lesson, and you are not choosing cursors or resize handles.
Direct answer
CSS 3D transforms use the same transform property as 2D, plus depth. Put perspective on a parent so children share a vanishing point (perspective-origin moves that point). rotateX and rotateY tilt around horizontal and vertical axes; rotateZ is the familiar flat spin. translateZ moves toward or away from the viewer. transform-style: preserve-3d keeps children in one 3D scene instead of flattening them. backface-visibility: hidden hides a face when it turns away — useful for a two-sided card. The layout slot still stays, as in 2D. Keep angles modest. Honor prefers-reduced-motion.
Why this is not 2D motion or UI chrome
The 2D transforms lesson taught plane motion: translate, rotate, scale, skew, and transform-origin for a hinge. Those still work. 3D adds a camera-like perspective and extra axes. User-interface CSS was interaction chrome (cursor, resize, caret-color, appearance). A rotated card does not need a lying hand cursor, and a textarea handle is not a vanishing point. If you only need a 2px lift, stay in 2D.
Perspective — put the camera on the parent
perspective is a length: smaller values feel closer and more dramatic; larger values feel flatter. Set it on the scene, not on every face, so siblings share one vanishing point.
.stage {
perspective: 800px;
perspective-origin: 50% 40%;
}
.card-3d {
transform: rotateY(18deg);
transform-origin: center;
}
You can also write transform: perspective(800px) rotateY(18deg) on one element. That camera belongs only to that box. For a cube or a row of tiles, parent perspective is calmer. perspective-origin is the vanishing-point cousin of transform-origin — it does not replace origin on the rotating face.
Axes — rotateX, rotateY, rotateZ
Think of a book. rotateY swings the cover left–right. rotateX nods it toward you or away. rotateZ spins it on the page — the 2D rotate() you already know.
.tile-y {
transform: rotateY(-22deg);
}
.tile-x {
transform: rotateX(12deg);
}
.tile-z {
transform: rotateZ(8deg);
}
Chain with 2D functions when you need both: rotateY(16deg) translateZ(24px). Order matters — later functions run in the local space of earlier ones. Prefer one transform list, same habit as the 2D lesson. Huge rotateX(80deg) on a whole article is a carnival, not a UI.
translateZ — distance, not a layout shove
Positive translateZ often comes toward the viewer when perspective is set. Neighbors in the document do not make room; only the paint moves, like 2D translate.
.stage {
perspective: 900px;
}
.pop-face {
transform: translateZ(40px) rotateY(-8deg);
}
.back-face {
transform: translateZ(-28px);
}
Use translateZ to separate faces of a scene, not to fake margin or z-index for ordinary stacking. z-index still orders overlapping boxes in 2D layout; 3D distance is a different knob. If the job is “lift a chip 2px,” translateY from the 2D lesson is enough.
preserve-3d — keep a scene, do not flatten
By default, a parent flattens transformed children (transform-style: flat). Nested faces then lose their 3D relationship. For a cube, a stacked cover, or a card with two sides, set preserve-3d on the rotating wrapper.
.scene {
perspective: 700px;
}
.rig {
transform-style: preserve-3d;
transition: transform 0.45s ease;
}
.rig.is-flipped {
transform: rotateY(180deg);
}
.face {
position: absolute;
inset: 0;
backface-visibility: hidden;
}
Every ancestor that should keep depth needs preserve-3d — one forgotten flat parent stamps the cube into a poster. perspective on the outer stage and preserve-3d on the inner rig is a common split: camera outside, joints inside.
backface-visibility — hide the reverse
When a face rotates past 90°, you would otherwise see it mirrored. backface-visibility: hidden drops that reverse. Pair two faces: front at rotateY(0), back at rotateY(180deg), both position: absolute on the same rig.
.card-rig {
position: relative;
transform-style: preserve-3d;
}
.card-front,
.card-back {
position: absolute;
inset: 0;
backface-visibility: hidden;
}
.card-back {
transform: rotateY(180deg);
}
Without hidden backs, you get a ghost of the front showing through. This is still not user-select: none and not appearance: none. You are hiding a painted face in 3D, not stripping a checkbox skin.
3D vs 2D transform vs UI chrome
📊 3D vs 2D transform vs UI chrome
| Tool | Job | Poor fit |
|---|---|---|
perspective + rotateX / rotateY / translateZ |
Depth scene: tilt, distance, shared vanishing point | Everyday 2px hover lift; pointer shape |
2D translate / rotate / scale / skew |
Motion in the plane; origin hinge | Building a cube without preserve-3d |
transform-style / backface-visibility |
Nested 3D faces; hide reversed sides | Caret color; textarea resize |
UI (cursor, resize, appearance…) |
Interaction chrome on controls | Vanishing points; card flips |
Optional Advanced already styled controls. Effects & Motion already taught 2D mapping. This session is the depth layer on transform. Mixing cursor: pointer onto a heading because it rotateYs does not make it 3D. Mixing perspective onto a login form does not replace resize on a notes field.
Calm habits
Keep perspective in the hundreds of pixels for UI cards (600px–1200px is a sane band). Do not set perspective: 40px on body. Prefer :hover / class toggles with a short transition on transform — same pairing as 2D. Gate flourish with prefers-reduced-motion: reduce (flatten to none or a tiny 2D nudge). Keyboard focus still needs a visible ring from earlier lessons; a flip is not a focus style.
.preview {
perspective: 1000px;
}
.preview .plate {
transition: transform 0.3s ease;
}
.preview:hover .plate {
transform: rotateY(-10deg) rotateX(4deg);
}
@media (prefers-reduced-motion: reduce) {
.preview .plate,
.preview:hover .plate {
transition: none;
transform: none;
}
}
That hover is a tilt, not the 2D translateY(-0.15rem) chip lift, and not resize: vertical on a field. If reduced-motion users would rather have a static card, believe them.
Try it yourself
- Wrap a card in
.stage { perspective: 800px; }and setrotateY(20deg)on the child — then moveperspectiveonto the childtransformand compare sibling tiles. - Build a two-face card:
preserve-3don the rig,backface-visibility: hiddenon both faces, back facerotateY(180deg). - Add
translateZ(32px)on a front plane; confirm layout neighbors do not shift. - Flip with a class +
transition: transform— then flatten withprefers-reduced-motion. - Do not solve this with
cursor,resize, or a 2D-onlytranslateYhover.
Common mistakes
perspective: 50pxonbodyso the whole page warps- Forgetting
preserve-3don the wrapper, then wondering why a cube looks flat - Treating the 2D chip-lift (
translateY) as a 3D lesson - Using UI chrome (
cursor,resize,appearance) as a stand-in for depth backface-visibilityon the stage instead of the faces- Ignoring
prefers-reduced-motionon largerotateYflips - Expecting 3D to change document flow the way
margindoes
Summary
- 3D transforms add depth to
transform: perspective,rotateX/rotateY,translateZ - Put perspective on the parent scene; use preserve-3d so nested faces stay in that space
- backface-visibility: hidden hides a face that has turned away
- Not a 2D hover-lift lesson and not user-interface chrome
- Keep angles modest; respect reduced motion
- This is the last CSS session in this course — Optional Advanced ends here; there is no further CSS slug in the map
🧠 Test Your Knowledge
Test Your Knowledge
Challenge yourself with this interactive quiz and see how well you understand the topic
📝 Instructions
- Read each question carefully
- Select the best answer for each question
- You can retake the quiz as many times as you want
- Your progress will be shown at the top