
CSS can create convincing 3D effects, but two properties are easy to mix up: perspective and transform-style. perspective controls the viewer distance and depth effect. transform-style decides whether children keep their 3D positions or get flattened into the parent plane.
Use them together when child elements need real depth.
What perspective does
perspective defines the distance between the viewer and the z=0 plane. Smaller values create stronger perspective. Larger values create a flatter, more subtle effect.
.stage {
width: 300px;
height: 200px;
perspective: 500px;
}
.element {
width: 100px;
height: 100px;
background: lightcoral;
transform: rotateY(45deg) translateZ(50px);
}
Set perspective on the parent of the transformed element. Think of it as the camera distance for the 3D scene.
Small vs large perspective values
.near {
perspective: 300px;
}
.far {
perspective: 1200px;
}
300px feels more dramatic. 1200px feels more subtle. There is no universal best value; choose based on the visual scale of the component.
What transform-style does
transform-style controls whether children are preserved in 3D:
.container-3d {
perspective: 400px;
transform-style: preserve-3d;
}
Values:
flat: default; children are flattened into the parent plane.preserve-3d: children keep their 3D positions.
If you have nested elements with translateZ, rotateY, or rotateX, you usually need preserve-3d on the parent that contains them.
Flat vs preserve-3d
.item-front {
transform: translateZ(20px);
}
.item-back {
transform: translateZ(-20px) rotateY(180deg);
}
With the default flat, the depth relationship may visually collapse. With preserve-3d, the front and back elements can appear at different depths.
How to combine them
The common setup:
.scene {
perspective: 600px;
}
.card {
transform-style: preserve-3d;
transition: transform 300ms ease;
}
.scene:hover .card {
transform: rotateY(180deg);
}
perspective is often on the outer scene. transform-style: preserve-3d is on the element whose children need depth.
Common mistakes
- Setting
perspectiveon the transformed element instead of its parent. - Forgetting
transform-style: preserve-3dfor nested 3D children. - Expecting 3D depth without
translateZ,rotateX, orrotateY. - Mixing 3D transforms with z-index assumptions without checking stacking context.
- Creating a visual effect that is hard to use on touch devices or with reduced motion preferences.
Related FreeMac guides
- For stacking context, read Why z-index Fails: Understanding CSS Stacking Context.
- For rendering performance, read Reflow and Repaint: Why CSS Animations Get Janky.
- For Motion in React, read Motion for React Guide: Animation, Gestures, and Exit Effects.
Continue reading
Reflow and Repaint: Why CSS Animations Get Janky
Understand browser rendering steps, reflow, repaint, compositing, and why transform and opacity are usually better animation targets than width, height, top, or left.
7 min readCSS initial, inherit, and unset: How to Choose
Understand CSS initial, inherit, and unset by first checking whether a property normally inherits, with practical examples for color, margin, reset styles, and components.
7 min readWhy z-index Fails: Understanding CSS Stacking Context
Understand why z-index: 9999 can still appear behind another element by learning stacking contexts, parent boundaries, transform, opacity, isolation, and portal-style fixes.
Subscribe to FreeMac
Weekly picks: free Mac software reviews, trusted source updates, alternatives, and low-friction guides.