3D transforms get botched constantly. The usual advice is to throw rotateX() at an element and hope it comes out looking like a polished interactive card, and it usually ends up flat and skewed because the rendering context gets ignored. I’ve seen plenty of “3D” UI components with no real depth, and the cause is almost always a parent container that never got a perspective value.
This walks through how the rotateX() function works and why your current setup might be failing. If you need more involved layout structures, I wrote a guide on stable zigzag CSS layouts using transforms. The 2D basics matter too, so read my guide to the CSS rotate() function before moving into the third dimension.
The technical syntax of rotateX()
The rotateX() function rotates an element around its horizontal axis, the X-axis, without deforming it. It tilts the element forward or backward in 3D space. According to the MDN documentation, the function takes a single angle as its argument.
/* Common Units */
.element {
transform: rotateX(45deg); /* 45 degrees backward */
transform: rotateX(-0.2turn); /* Counter-clockwise rotation */
transform: rotateX(1.57rad); /* Approximately 90 degrees */
}
A positive value tilts the top of the element away from you, and a negative value pulls it toward you. You can use units like deg, grad, rad, or turn, but I stick to degrees for maintainability unless an animation needs heavy math.
The perspective gotcha (war story)
Years back, in the WordPress 4.8 days, a client wanted a “flipping pricing table.” I spent three hours debugging why the rotateX() animation looked like a squashed 2D image instead of a 3D flip. The culprit was the missing perspective property on the parent container. Without it, the browser has no idea how far the user sits from the Z-plane, so it flattens the rotation.
The fix is to set perspective on the parent, and to add transform-style: preserve-3d when you have nested 3D elements. A proper setup looks like this:
/* The Correct 3D Setup */
.container {
perspective: 1000px; /* Essential for 3D depth */
}
.card {
transform-style: preserve-3d;
transition: transform 0.6s ease;
}
.card:hover {
transform: rotateX(180deg);
}
A practical use case: the 3D accordion
Instead of the usual slide-down, you can use rotateX() for a staggered “unfolding” animation, which tends to feel more polished to the user. Set the transform-origin to the top and the element hinges downward like a door.
.accordion-content {
transform-origin: top;
transform: rotateX(-90deg);
opacity: 0;
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
}
.accordion-item.is-open .accordion-content {
transform: rotateX(0deg);
opacity: 1;
}
The content then unfolds into view. If you want the deeper implementation details, the W3C specification covers how these matrices get calculated, though most devs never need to touch the raw math.
If this rotateX() work is eating your dev hours, I can take it off your plate. I’ve been wrestling with WordPress since the 4.x days.
Takeaway for senior developers
Shipping 3D UI comes down to the perspective and backface-visibility, not the rotation itself. If your rotateX() looks “off,” check the parent container first. And keep in mind that 3D transforms can trigger hardware acceleration, which helps performance but sometimes leaves text a little blurry if you are not careful. Always test on Safari.