3D Transforms & Perspective
Introduction to 3D Space in CSS
CSS 3D transforms extend the 2D transform model by adding a third axis: the Z-axis, which represents depth. While 2D transforms operate on the X (horizontal) and Y (vertical) axes, 3D transforms allow you to rotate, translate, and scale elements along all three axes, creating the illusion of three-dimensional space directly in the browser. This opens the door to immersive effects like card flips, rotating cubes, carousels, and layered interfaces that feel tangible and interactive.
To make 3D transforms visible, you need to establish a viewing context using perspective. Without perspective, 3D rotations appear flat because the browser renders them using an orthographic (parallel) projection rather than a perspective projection. Think of perspective as defining where the viewer is standing relative to the screen -- it controls how dramatically depth is perceived.
Example: 3D Transform vs Flat Transform
/* Without perspective: rotateY looks like scaleX */
.flat {
transform: rotateY(45deg);
/* This looks like a horizontal squish, not a rotation */
}
/* With perspective on the parent: true 3D appearance */
.parent {
perspective: 800px;
}
.parent .child {
transform: rotateY(45deg);
/* Now this looks like a card rotated in 3D space */
}
The perspective Property vs the perspective() Function
There are two ways to apply perspective in CSS, and they behave differently. Understanding the distinction is critical for building correct 3D effects.
The perspective Property (on a Parent)
When you apply the perspective property to a parent element, all children share the same perspective viewpoint. This creates a consistent 3D scene where elements closer to the edge appear to have more dramatic foreshortening, just like objects in the real world. The value represents the distance from the viewer to the z=0 plane.
Example: perspective Property on Parent
.scene {
perspective: 1000px;
/* All children share this perspective */
/* Lower values = more dramatic 3D effect */
/* Higher values = subtler 3D effect */
}
.scene .card-1 {
transform: rotateY(30deg);
/* Appears to rotate in shared 3D space */
}
.scene .card-2 {
transform: rotateY(-30deg);
/* Rotates from the same viewpoint as card-1 */
}
The perspective() Function (on the Element)
When you use perspective() as a transform function on the element itself, each element gets its own individual perspective. This means every element is viewed from directly in front of it, regardless of its position on the page. Elements positioned near the edges of a container do not show additional foreshortening.
Example: perspective() Function on Elements
/* Each element has its own perspective */
.card-1 {
transform: perspective(1000px) rotateY(30deg);
/* Viewed from directly in front of this card */
}
.card-2 {
transform: perspective(1000px) rotateY(-30deg);
/* Viewed from directly in front of this card too */
/* Both cards look the same despite different positions */
}
/* IMPORTANT: perspective() must come FIRST in the transform chain */
.correct {
transform: perspective(800px) rotateY(45deg);
}
.incorrect {
transform: rotateY(45deg) perspective(800px);
/* perspective after rotation produces unexpected results */
}
perspective property on a parent when you want multiple children to exist in the same 3D scene (like a card grid or a 3D carousel). Use the perspective() function when you want each element to have its own independent 3D effect (like individual card hover animations). The property approach is more common for complex 3D compositions.Choosing a Perspective Value
The perspective value determines how dramatic the 3D effect appears. It represents the virtual distance between the viewer and the z=0 plane in pixels. Smaller values create a more dramatic, exaggerated 3D effect (like holding an object very close to your eyes), while larger values create a subtler, more natural effect (like viewing from a distance).
Example: Perspective Value Comparison
/* Very dramatic -- close to the element */
.dramatic {
perspective: 200px;
}
/* Moderate -- good default for most effects */
.moderate {
perspective: 800px;
}
/* Subtle -- distant viewpoint */
.subtle {
perspective: 2000px;
}
/* Recommended ranges for common effects: */
/* Card flips: 600px - 1200px */
/* 3D cubes: 800px - 1500px */
/* Subtle hover effects: 1000px - 2000px */
/* Dramatic page transitions: 300px - 600px */
perspective-origin -- Moving the Viewpoint
The perspective-origin property defines where the viewer is looking from. By default, it is set to the center of the element (50% 50%). Changing it shifts the vanishing point, making elements appear as if viewed from a different angle. This is applied to the same parent element that has the perspective property.
Example: perspective-origin Values
.scene {
perspective: 1000px;
perspective-origin: center center;
/* Default: looking straight at the center */
}
/* Viewing from the top-left */
.scene-top-left {
perspective: 1000px;
perspective-origin: left top;
/* Elements rotate as if viewed from upper-left */
}
/* Viewing from the right side */
.scene-right {
perspective: 1000px;
perspective-origin: 100% 50%;
/* Elements rotate as if viewed from the right */
}
/* Custom position */
.scene-custom {
perspective: 1000px;
perspective-origin: 25% 75%;
}
/* Interactive: change perspective-origin on mouse move */
/* (typically done via JavaScript) */
.scene-interactive {
perspective: 1000px;
perspective-origin: var(--mouse-x, 50%) var(--mouse-y, 50%);
}
3D Rotation Functions
CSS provides rotation functions for each of the three axes, plus a combined function for arbitrary axis rotation.
rotateX() -- Rotating Around the Horizontal Axis
The rotateX() function rotates an element around the horizontal X-axis, like a door flipping forward or backward. Positive values tilt the top of the element away from the viewer (backward), while negative values tilt the top toward the viewer (forward).
Example: rotateX() Demonstrations
.scene {
perspective: 800px;
}
/* Tilt backward -- top moves away */
.tilt-back {
transform: rotateX(30deg);
}
/* Tilt forward -- top moves toward viewer */
.tilt-forward {
transform: rotateX(-30deg);
}
/* Full flip along horizontal axis */
.flip-vertical {
transition: transform 0.6s ease;
}
.flip-vertical:hover {
transform: rotateX(180deg);
}
rotateY() -- Rotating Around the Vertical Axis
The rotateY() function rotates an element around the vertical Y-axis, like a revolving door or a page turning in a book. Positive values rotate the right side away from the viewer, while negative values rotate the left side away.
Example: rotateY() Demonstrations
.scene {
perspective: 800px;
}
/* Rotate right side away */
.rotate-right {
transform: rotateY(45deg);
}
/* Rotate left side away */
.rotate-left {
transform: rotateY(-45deg);
}
/* Classic card flip on Y axis */
.card-flip {
transition: transform 0.8s ease;
}
.card-flip:hover {
transform: rotateY(180deg);
}
rotateZ() -- Rotating Around the Depth Axis
The rotateZ() function rotates an element around the Z-axis, which is the same as the 2D rotate() function. It spins the element in the plane of the screen. It is included here for completeness, as it is part of the 3D rotation family.
Example: rotateZ()
/* These are equivalent */
.spin-2d {
transform: rotate(45deg);
}
.spin-z {
transform: rotateZ(45deg);
}
/* Combining all three rotation axes */
.complex-rotation {
transform: rotateX(20deg) rotateY(30deg) rotateZ(10deg);
}
rotate3d() -- Rotating Around an Arbitrary Axis
The rotate3d() function lets you define a custom axis of rotation using a vector (x, y, z) and an angle. The axis vector does not need to be normalized (the browser normalizes it internally). This is useful when you need a rotation that does not align with any of the standard axes.
Example: rotate3d() Custom Axis
/* rotate3d(x, y, z, angle) */
/* Same as rotateX(45deg) */
.around-x {
transform: rotate3d(1, 0, 0, 45deg);
}
/* Same as rotateY(45deg) */
.around-y {
transform: rotate3d(0, 1, 0, 45deg);
}
/* Diagonal axis rotation (X and Y combined) */
.diagonal {
transform: rotate3d(1, 1, 0, 45deg);
/* Rotates around a 45-degree diagonal axis */
}
/* Custom axis with Z component */
.custom-axis {
transform: rotate3d(1, 2, 0.5, 60deg);
}
3D Translation: translateZ() and translate3d()
The translateZ() function moves an element along the Z-axis, toward or away from the viewer. Positive values move the element closer (making it appear larger when perspective is applied), while negative values push it further away (making it appear smaller). The translate3d() function combines all three axes in one function call.
Example: translateZ() and translate3d()
.scene {
perspective: 1000px;
}
/* Move toward the viewer (appears larger) */
.closer {
transform: translateZ(200px);
}
/* Move away from the viewer (appears smaller) */
.further {
transform: translateZ(-200px);
}
/* Combine all three axes */
.move-3d {
transform: translate3d(50px, -20px, 100px);
/* X: 50px right, Y: 20px up, Z: 100px closer */
}
/* Hover effect: element pops toward the viewer */
.pop-card {
transition: transform 0.3s ease;
}
.pop-card:hover {
transform: translateZ(50px);
/* Card appears to pop out of the screen */
}
/* IMPORTANT: translateZ with percentages */
/* Unlike translateX/Y, translateZ does NOT accept % values */
/* You must use absolute length units (px, em, rem, etc.) */
translateZ() function is often used to promote elements to their own GPU compositing layer (via translateZ(0) or translate3d(0, 0, 0)), which can improve animation performance. However, this is a hack that has been largely superseded by will-change: transform, which is the proper way to hint layer promotion to the browser.3D Scaling: scale3d()
The scale3d() function scales an element along all three axes simultaneously. The Z-axis scaling only has a visible effect on elements that are already transformed in 3D space (they have children positioned at different Z depths).
Example: scale3d()
/* Scale all three axes */
.scale-all {
transform: scale3d(1.5, 1.5, 1.5);
}
/* scaleZ only matters for elements with 3D children */
.cube-container {
transform-style: preserve-3d;
transform: scale3d(1, 1, 2);
/* Stretches the cube along the Z axis */
}
transform-style: preserve-3d vs flat
The transform-style property determines whether the children of a transformed element exist in 3D space or are flattened into the plane of their parent. This is one of the most important properties for building 3D compositions.
Example: transform-style Comparison
/* Default: children are flattened */
.flat-parent {
transform-style: flat;
transform: rotateY(30deg);
}
.flat-parent .child {
transform: rotateX(45deg);
/* The child's rotation is flattened into the parent's plane */
/* It does NOT rotate independently in 3D space */
}
/* preserve-3d: children maintain their own 3D positioning */
.preserve-parent {
transform-style: preserve-3d;
transform: rotateY(30deg);
}
.preserve-parent .child {
transform: rotateX(45deg);
/* The child rotates in true 3D space relative to the parent */
/* Both transforms are composited in the same 3D scene */
}
transform-style to behave as flat even when you set preserve-3d. These include overflow: hidden (or auto or scroll), opacity with a value less than 1, filter, clip-path, and isolation: isolate. If your 3D effect suddenly looks flat, check whether any of these properties are applied to the parent or an ancestor element.backface-visibility -- Hiding the Back Side
When an element is rotated more than 90 degrees, its back face becomes visible. By default, the browser shows a mirrored version of the front content. The backface-visibility property lets you hide the back face, which is essential for card flip effects where you want to show entirely different content on the back.
Example: backface-visibility
/* Default: back face is visible (mirrored content) */
.show-back {
backface-visibility: visible;
}
/* Hide the back face */
.hide-back {
backface-visibility: hidden;
/* When rotated past 90deg, the element disappears */
}
/* This is ESSENTIAL for the card flip pattern */
.card-front {
backface-visibility: hidden;
/* Hidden when the card flips to show the back */
}
.card-back {
backface-visibility: hidden;
transform: rotateY(180deg);
/* Pre-rotated, hidden by default */
/* Becomes visible when parent rotates 180deg */
}
Building a 3D Card Flip Effect
The card flip is one of the most popular 3D CSS effects. It creates a two-sided card that flips to reveal different content on each side. This pattern requires all the 3D concepts covered so far: perspective, preserve-3d, backface-visibility, and rotateY.
Example: Complete 3D Card Flip
<style>
.card-container {
width: 320px;
height: 420px;
perspective: 1000px;
}
.card {
position: relative;
width: 100%;
height: 100%;
transform-style: preserve-3d;
transition: transform 0.8s cubic-bezier(0.4, 0, 0.2, 1);
}
/* Flip on hover */
.card-container:hover .card {
transform: rotateY(180deg);
}
.card-front,
.card-back {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
backface-visibility: hidden;
border-radius: 16px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 32px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12);
}
.card-front {
background: linear-gradient(135deg, var(--primary), var(--primary-light));
color: white;
}
.card-back {
background: var(--bg-white);
color: var(--text-dark);
transform: rotateY(180deg);
/* Pre-rotated so it is hidden initially */
/* When parent rotates 180deg, this becomes visible */
}
</style>
<div class="card-container">
<div class="card">
<div class="card-front">
<h3>Front Side</h3>
<p>Hover to flip</p>
</div>
<div class="card-back">
<h3>Back Side</h3>
<p>Hidden content revealed</p>
</div>
</div>
</div>
aria-hidden="true" on the back face and toggle it via JavaScript when the card is flipped. Also consider providing a click-based flip for touch devices, since hover does not work on mobile.Flipping on the X-axis
The same pattern works for vertical flips by changing rotateY to rotateX. The back face is pre-rotated on the same axis.
Example: Vertical Card Flip (X-axis)
.card-container:hover .card {
transform: rotateX(180deg);
}
.card-back {
transform: rotateX(180deg);
/* Pre-rotated on X instead of Y */
}
Building a 3D Cube with CSS
A CSS cube is a classic exercise that demonstrates mastery of 3D transforms. Each face is an absolutely positioned element rotated and translated into position. The cube container uses transform-style: preserve-3d to keep all faces in the same 3D space.
Example: Complete 3D Cube
<style>
.cube-scene {
width: 200px;
height: 200px;
perspective: 600px;
margin: 100px auto;
}
.cube {
width: 200px;
height: 200px;
position: relative;
transform-style: preserve-3d;
transform: rotateX(-25deg) rotateY(30deg);
transition: transform 1s ease;
}
.cube:hover {
transform: rotateX(-25deg) rotateY(210deg);
}
.cube-face {
position: absolute;
width: 200px;
height: 200px;
display: flex;
align-items: center;
justify-content: center;
font-size: 24px;
font-weight: bold;
border: 2px solid rgba(0, 0, 0, 0.1);
opacity: 0.85;
}
/* Front face: no rotation, just move forward */
.cube-front {
background: rgba(255, 0, 0, 0.6);
transform: translateZ(100px);
}
/* Back face: rotate 180 degrees, move forward */
.cube-back {
background: rgba(0, 255, 0, 0.6);
transform: rotateY(180deg) translateZ(100px);
}
/* Right face: rotate 90 degrees on Y, move forward */
.cube-right {
background: rgba(0, 0, 255, 0.6);
transform: rotateY(90deg) translateZ(100px);
}
/* Left face: rotate -90 degrees on Y, move forward */
.cube-left {
background: rgba(255, 255, 0, 0.6);
transform: rotateY(-90deg) translateZ(100px);
}
/* Top face: rotate -90 degrees on X, move forward */
.cube-top {
background: rgba(255, 0, 255, 0.6);
transform: rotateX(90deg) translateZ(100px);
}
/* Bottom face: rotate 90 degrees on X, move forward */
.cube-bottom {
background: rgba(0, 255, 255, 0.6);
transform: rotateX(-90deg) translateZ(100px);
}
</style>
<div class="cube-scene">
<div class="cube">
<div class="cube-face cube-front">Front</div>
<div class="cube-face cube-back">Back</div>
<div class="cube-face cube-right">Right</div>
<div class="cube-face cube-left">Left</div>
<div class="cube-face cube-top">Top</div>
<div class="cube-face cube-bottom">Bottom</div>
</div>
</div>
The key insight for building a cube is that every face is translated along the Z-axis by half the cube's width after being rotated to face the correct direction. The translateZ(100px) pushes each face outward from the center by 100px (half of the 200px cube). The rotation before the translation ensures the face is oriented correctly before being pushed out.
translateZ works for every face after the rotation aligns it properly.3D Carousel Concept
A 3D carousel arranges items in a circle using rotateY and translateZ. Each item is rotated around the Y-axis by an equal fraction of 360 degrees, then pushed outward with translateZ. The container rotates to show different items.
Example: 3D Carousel Layout
<style>
.carousel-scene {
width: 250px;
height: 180px;
perspective: 1200px;
margin: 100px auto;
}
.carousel {
width: 100%;
height: 100%;
position: relative;
transform-style: preserve-3d;
transition: transform 1s ease;
}
/* 6 items: each rotated by 60deg (360 / 6) */
.carousel-item {
position: absolute;
width: 250px;
height: 180px;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
font-weight: bold;
background: var(--bg-white);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
}
/* Calculate translateZ based on item count and width */
/* For 6 items: translateZ = width / (2 * tan(pi/6)) approx 217px */
.carousel-item:nth-child(1) { transform: rotateY(0deg) translateZ(217px); }
.carousel-item:nth-child(2) { transform: rotateY(60deg) translateZ(217px); }
.carousel-item:nth-child(3) { transform: rotateY(120deg) translateZ(217px); }
.carousel-item:nth-child(4) { transform: rotateY(180deg) translateZ(217px); }
.carousel-item:nth-child(5) { transform: rotateY(240deg) translateZ(217px); }
.carousel-item:nth-child(6) { transform: rotateY(300deg) translateZ(217px); }
/* Rotate to show specific item */
.carousel.show-1 { transform: rotateY(0deg); }
.carousel.show-2 { transform: rotateY(-60deg); }
.carousel.show-3 { transform: rotateY(-120deg); }
.carousel.show-4 { transform: rotateY(-180deg); }
/* Auto-rotate animation */
@keyframes spin-carousel {
from { transform: rotateY(0deg); }
to { transform: rotateY(-360deg); }
}
.carousel.auto-spin {
animation: spin-carousel 20s linear infinite;
}
</style>
<div class="carousel-scene">
<div class="carousel">
<div class="carousel-item">1</div>
<div class="carousel-item">2</div>
<div class="carousel-item">3</div>
<div class="carousel-item">4</div>
<div class="carousel-item">5</div>
<div class="carousel-item">6</div>
</div>
</div>
The formula for the translateZ radius in a carousel with n items of width w is: radius = w / (2 * tan(PI / n)). For 6 items with a width of 250px, this gives approximately 217px. Adjusting this value changes how spread out the items appear.
Performance Considerations for 3D Transforms
3D transforms are GPU-accelerated, which makes them performant for animations. However, there are important considerations to keep in mind.
- Layer creation. Elements with 3D transforms are promoted to their own compositing layers. Each layer consumes GPU memory. Too many layers (especially large ones) can degrade performance on mobile devices with limited GPU memory.
- preserve-3d and painting. Elements within a
preserve-3dcontext cannot be independently optimized by the browser. The entire 3D scene must be composited together, which can be more expensive than flat rendering. - Avoid animating perspective. Animating the
perspectiveproperty forces the browser to recalculate the entire 3D scene every frame. Instead, animate the child transforms while keeping perspective constant. - Reduce unnecessary 3D. If an element does not need to participate in 3D space, do not apply
preserve-3dto its parent. The defaultflatvalue is more performant. - Use will-change sparingly. Apply
will-change: transformonly to elements that will actually be animated, and remove it after the animation completes to free GPU resources.
Example: Performance-Optimized 3D Animation
/* Prepare for animation only when needed */
.card-container {
perspective: 1000px;
}
.card {
transform-style: preserve-3d;
transition: transform 0.6s ease;
/* will-change is not set by default */
}
/* Add will-change right before the animation triggers */
.card-container:hover .card {
will-change: transform;
}
/* After transition ends, remove will-change via JS:
element.addEventListener('transitionend', () => {
element.style.willChange = 'auto';
});
*/
Browser Support and Fallbacks
3D transforms are well supported in all modern browsers. However, there are a few nuances to be aware of for cross-browser compatibility.
Example: Progressive Enhancement for 3D
/* Base styles work without 3D */
.card-front,
.card-back {
position: absolute;
width: 100%;
height: 100%;
transition: opacity 0.3s ease;
}
.card-back {
opacity: 0;
}
.card-container:hover .card-front {
opacity: 0;
}
.card-container:hover .card-back {
opacity: 1;
}
/* Enhanced 3D flip for supporting browsers */
@supports (transform-style: preserve-3d) {
.card {
transform-style: preserve-3d;
transition: transform 0.8s ease;
}
.card-front,
.card-back {
backface-visibility: hidden;
transition: none;
opacity: 1;
}
.card-back {
transform: rotateY(180deg);
opacity: 1;
}
.card-container:hover .card {
transform: rotateY(180deg);
}
.card-container:hover .card-front,
.card-container:hover .card-back {
opacity: 1;
}
}
Practical Examples
Interactive 3D Button
A button that appears to tilt in 3D when hovered, creating a tangible, pressable feeling.
Example: 3D Tilt Button
.button-scene {
perspective: 600px;
display: inline-block;
}
.button-3d {
padding: 16px 40px;
font-size: 18px;
font-weight: 600;
color: white;
background: var(--primary);
border: none;
border-radius: 8px;
cursor: pointer;
transition: transform 0.2s ease, box-shadow 0.2s ease;
transform-origin: center bottom;
}
.button-3d:hover {
transform: rotateX(-15deg);
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.2);
}
.button-3d:active {
transform: rotateX(-5deg) translateY(2px);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
}
3D Panel Reveal
A panel that swings open like a door to reveal content behind it.
Example: Door-Opening Panel
.panel-scene {
perspective: 1500px;
width: 400px;
height: 300px;
position: relative;
}
.panel-door {
width: 100%;
height: 100%;
background: var(--primary);
color: white;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transform-origin: left center;
transition: transform 0.6s cubic-bezier(0.4, 0, 0.2, 1);
position: absolute;
z-index: 2;
border-radius: 12px;
}
.panel-door:hover {
transform: rotateY(-110deg);
}
.panel-content {
width: 100%;
height: 100%;
background: var(--bg-light);
display: flex;
align-items: center;
justify-content: center;
padding: 24px;
border-radius: 12px;
position: absolute;
z-index: 1;
}
Layered Card Stack
Multiple cards stacked in 3D space that fan out on hover.
Example: 3D Card Stack Fan-Out
.stack-scene {
perspective: 1000px;
width: 300px;
height: 200px;
position: relative;
}
.stack-scene:hover .stack-card:nth-child(1) {
transform: translateZ(0px) rotateY(0deg);
}
.stack-scene:hover .stack-card:nth-child(2) {
transform: translateZ(-30px) rotateY(10deg) translateX(30px);
}
.stack-scene:hover .stack-card:nth-child(3) {
transform: translateZ(-60px) rotateY(20deg) translateX(60px);
}
.stack-card {
position: absolute;
width: 100%;
height: 100%;
background: var(--bg-white);
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
transition: transform 0.5s ease;
transform-style: preserve-3d;
}
Combining 2D and 3D Transforms
You can freely mix 2D and 3D transform functions in a single transform declaration. 2D functions like translateX(), scale(), and skew() work alongside 3D functions like rotateY() and translateZ().
Example: Mixed 2D and 3D Transforms
.combined {
transform: perspective(800px)
translateY(-10px)
rotateX(5deg)
rotateY(15deg)
scale(1.02);
/* All functions compose into a single transformation matrix */
}
/* Practical: tilted card with hover lift */
.tilt-card {
transition: transform 0.4s ease;
}
.tilt-card:hover {
transform: perspective(1000px)
translateY(-8px)
rotateX(3deg)
rotateY(-3deg)
scale(1.03);
}
Debugging 3D Transforms
Debugging 3D CSS can be challenging because elements may become invisible or appear distorted. Here are practical tips for troubleshooting.
- Check perspective. If your 3D transforms look flat, ensure the parent has a
perspectivevalue. Without it, rotateX and rotateY produce flat-looking results. - Verify preserve-3d. If children do not appear in 3D space relative to their parent, make sure
transform-style: preserve-3dis set on the parent. - Look for property conflicts. Remember that
overflow: hidden,opacity < 1,filter, andclip-pathon an ancestor will forcetransform-styletoflat. - Use semi-transparent backgrounds. When building 3D objects like cubes, use semi-transparent colors so you can see through faces and verify their positioning.
- Reduce perspective temporarily. Use a very low perspective value (like 200px) to exaggerate the 3D effect and make positioning errors obvious.
- Browser DevTools. Chrome DevTools has a 3D view feature (in the Layers panel) that lets you inspect the compositing layers and see your 3D elements from different angles.
Practice Exercise
Build a project that demonstrates each major concept from this lesson. (1) Create a 3D card flip component that reveals different content on each side. The front should show an image and title, the back should show a description and a link. Use perspective on the parent, preserve-3d on the card, and backface-visibility: hidden on both faces. (2) Build a complete CSS 3D cube that can be rotated to show different faces using radio buttons or CSS classes toggled by hover. Each face should have a distinct color and label. (3) Create a 3D carousel with at least five items. Calculate the correct translateZ radius for your item count and width. Add a slow automatic rotation animation with @keyframes. (4) Build a panel that opens like a door using rotateY with a left transform-origin. (5) Use the @supports rule to provide a fallback opacity-based animation for the card flip in case a browser does not support preserve-3d. Test all components across Chrome, Firefox, and Safari. Inspect the compositing layers in DevTools to understand how the browser handles your 3D elements.