Rotations with CSS3: A practical guide to rotating elements in 2D and 3D
- 👤 Andrés Cruz
One of the most interesting aspects of CSS3 is the use of transformations, since several of them allow you to perform 3D operations and understand the potential of web technologies.
The first thing I tried was rotation. I was surprised by how easy it was to rotate any HTML element and also how easy it was for something to look strange if you didn't understand the origin of the transformation. In this article, I show you how to rotate elements with CSS3, when to use each type of rotation, and how to avoid common errors.
In this post, we will see how to perform rotations on any HTML element using the CSS3 transform property.
What is CSS rotation and what is it for
CSS rotations allow you to rotate elements in two or three dimensions using the transform property. It is one of the most useful transformations for animations, micro-interactions, loaders, "flip" cards, hover effects, and interactive components.
In my case, I always start by explaining rotate() because it is the most intuitive: you tell it how many degrees and CSS does the rest.
How transform and the rotate() functions work
The transform property in CSS3 allows you to apply 2D and 3D transformations to HTML elements on a web page; in this entry, we are interested in learning how to perform one of these transformations, which are rotations; the possible properties to use are the following:
rotate(angle): Defines a rotation in its 2D space.rotate3d(x,y,z,angle): Defines a 3D rotation across all axes.rotateX(angle): Defines a 3D rotation around the X-axis.rotateY(angle): Defines a 3D rotation around the Y-axis.rotateZ(angle): Defines a 3D rotation around the Z-axis.
The basic syntax:
.elemento {
transform: rotate(45deg);
}- A positive value 👉 rotates clockwise.
- A negative value 👉 rotates counter-clockwise.
Considerations
- The angle must be defined in degrees (
degfor its acronym in English). - It is advisable to use web browser prefixes to guarantee compatibility.
- With all the properties seen above, it is possible to define rotations in a 2D and 3D space according to an origin point defined by the
transform-originproperty.
Defining a CSS rule for transformations
div
{
transform:rotate(7deg);
-ms-transform:rotate(7deg); /* IE 9 en adelante*/
-webkit-transform:rotate(7deg); /* Opera, Chrome, y Safari */
}It is important to clarify that transformations or the transform property have already been standardized in modern browsers and, therefore, it is not necessary to add prefixes such as -ms or -webkit, to name a few.
2D Rotations: rotate() and rotateZ()
If you only need a flat rotation (like a compass, a rotating icon, or a simple loader), use rotate().
Clockwise and counter-clockwise:
.caja-horaria { transform: rotate(30deg); }
.caja-antihoraria { transform: rotate(-30deg); }Rotate an element 45°:
.caja { width: 120px; height: 120px; background: #ddd; transform: rotate(45deg);}This rotation is done around the center of the element unless otherwise indicated (we will talk about transform-origin, which is key, later).
3D Rotations: rotateX(), rotateY() and rotate3d()
This is where the fun begins. Three-dimensional rotations add depth, ideal for flip cards, “perspective” effects, or more dynamic UI.
When to use each axis
- rotateX() → rotates forward/backward (lifting effect)
- rotateY() → rotates sideways (like a door)
- rotateZ() → equivalent to rotate(), flat rotation
- rotate3d(x, y, z, angle) → advanced rotation combining axes
Practical case: combined rotations
.tarjeta { transform: rotateX(30deg) rotateY(20deg);}When I first tried 3D rotations, I noticed that some looked “squashed”. This happens because there is no perspective defined. If you want them to look realistic:
.contenedor { perspective: 800px;}The role of transform-origin: why your rotations sometimes look wrong
When you start testing transformations, you will notice that, even if you rotated correctly, the result could be strange if the origin was not configured correctly.
Changing the center of rotation
.caja {
transform-origin: top left;
transform: rotate(45deg);
}Valid values:
- Keywords: top, bottom, left, right, center
- Percentages: 25% 75%
- Absolute values: 20px 30px
Examples with top/left/center:
- top left → the rotation seems to start from the corner
- center (default) → ideal for icons or loaders
- bottom → useful for elements with a fixed pivot (hands of a clock, doors, etc.)
Examples of rotations on elements
In all examples, rotations will be applied to the image container divs from 0 degrees to 360 degrees in an infinite loop with the help of a little JavaScript.
rotate(angle):
With the rotate property, it is possible to define rotations in a 2D space; depending on the sign (positive or negative) of the specified angle, one of the following conditions may occur:
- Clockwise or to the right if the angle is positive.
- Counter-clockwise or to the left if the angle is negative.
First, we will see an example of the above; it basically consists of applying the rotations of a pair of images clockwise when the degree is positive and counter-clockwise otherwise:
Clockwise rotations with a positive degree:
Clockwise Rotations
rotate()Counter-clockwise rotations with a negative degree:
Counter-clockwise Rotations
rotate()
rotateX(angle), rotateY(angle) and rotateZ(angle):
As you may have noticed, the name of the property indicates the axis affected by the rotations:
Rotation on the X axis
Rotation on the X axis
rotateX()Rotation on the Y axis
Rotation on the Y axis
rotateY()Rotation on the Z axis
Rotation on the Z axis

rotateZ()
rotate3d(angle)
Now we will apply the rotation on all three axes with a single property:
Rotation on the X, Y, and Z axes
Rotation on the X, Y, and Z axes

If we wanted the element to only rotate on the X-axis with the rotate3d() property, we could do the following:
rotateX(angle) is equivalent to rotate3d(1, 0, 0, angle).
Flip Card Component (HTML + CSS)
This is one of the most elegant uses of 3D rotations. It is perfect for portfolios, product cards, or UI with "behind" information.
When I taught this effect, I always explained that the key was to combine rotateY() with backface-visibility: hidden to avoid the mirror effect.
<div class="flip-card">
<div class="flip-card-inner">
<div class="flip-card-front">
Front
</div>
<div class="flip-card-back">
Back
</div>
</div>
</div>.flip-card {
width: 200px;
height: 260px;
perspective: 1000px;
}
.flip-card-inner {
width: 100%;
height: 100%;
transition: transform 0.8s;
transform-style: preserve-3d;
}
.flip-card:hover .flip-card-inner {
transform: rotateY(180deg);
}
.flip-card-front,
.flip-card-back {
position: absolute;
width: 100%;
height: 100%;
backface-visibility: hidden;
display: flex;
justify-content: center;
align-items: center;
font-size: 1.4rem;
font-weight: bold;
color: #fff;
border-radius: 10px;
}
.flip-card-front {
background: #4a90e2;
}
.flip-card-back {
background: #50c878;
transform: rotateY(180deg);
}Expected result:
- The card flips on mouse hover.
- The front and back sides align perfectly thanks to preserve-3d.
- The rotation is smooth thanks to the transition.
Rotating Loader: 3 examples (simple, dual, and 3D)
Here are three loaders ready to copy and paste.
When I used to do animation demos, these were my classics because they are simple and very visual.
1. Simple Rotating Loader (rotate() only)
<div class="loader-simple"></div>
.loader-simple {
width: 40px;
height: 40px;
border: 4px solid #ddd;
border-top-color: #4a90e2;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}2. Dual Loader (two rings rotating in opposite directions)
<div class="loader-dual">
<div></div>
<div></div>
</div>CSS
.loader-dual {
position: relative;
width: 50px;
height: 50px;
}
.loader-dual div {
position: absolute;
inset: 0;
border: 4px solid transparent;
border-radius: 50%;
animation: giro-dual 1.2s linear infinite;
}
.loader-dual div:nth-child(1) {
border-top-color: #e24a4a;
}
.loader-dual div:nth-child(2) {
border-bottom-color: #4a90e2;
animation-direction: reverse;
}
@keyframes giro-dual {
to { transform: rotate(360deg); }
}This one looks more professional because it combines two opposing rotations.
3. 3D Loader (rotateX / rotateY with perspective)
<div class="loader-3d"></div>
.loader-3d {
width: 40px;
height: 40px;
border: 4px solid #4a90e2;
border-radius: 50%;
animation: giro3d 1.4s ease-in-out infinite;
transform-style: preserve-3d;
}
@keyframes giro3d {
0% { transform: rotateX(0) rotateY(0); }
50% { transform: rotateX(180deg) rotateY(0); }
100% { transform: rotateX(180deg) rotateY(180deg); }
}The result is a "globe" effect rotating on two axes. If you add perspective to a parent container, the effect becomes even more realistic.
Animating a rotation: from 0° to 360°
If you want a loader or an infinite spin, CSS is enough.
Animation with pure CSS (keyframes)
rotando {
animation: giro 3s linear infinite;
}
@keyframes giro {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}In my first experiments, I used JavaScript for this, but today CSS is totally sufficient for most things.
JavaScript alternative for infinite loops
let grados = 0;
setInterval(() => {
grados++;
elemento.style.transform = `rotate(${grados}deg)`;
}, 10);Rotating only the background image (pseudoelement technique)
CSS does not allow the background image to be rotated directly.
The solution is to use a pseudoelement.
.caja {
position: relative;
overflow: hidden;
}
.caja::before {
content: "";
position: absolute;
inset: -50%;
background: url('imagen.jpg') center/cover no-repeat;
transform: rotate(45deg);
z-index: -1;
}Preventing content from rotating
If you rotate an entire container, the content inside it also rotates.
To reverse it:
.caja {
transform: rotate(30deg);
}
.caja::before {
transform: rotate(-30deg);
}Common errors and how to solve them
- Elements that "squash"
- This happens a lot when using rotateX or rotateY without perspective.
Solution: apply perspective.
- This happens a lot when using rotateX or rotateY without perspective.
- When the rotation is not noticeable or disappears
- It may be rotating out of the viewport.
Solution: increase width/height or use overflow: visible.
- It may be rotating out of the viewport.
- Overlap issues
- 2D rotations do not take up more space; they can collide with other elements.
Solution: extra margins or wider containers.
- 2D rotations do not take up more space; they can collide with other elements.
Compatibility and using browser prefixes
Although you almost don't need them today, it's still important to include them in legacy projects.
I remember that when I applied rotations on old sites, the -ms- prefix was mandatory for everything to work in IE9.
.elemento {
-webkit-transform: rotate(45deg);
-moz-transform: rotate(45deg);
-ms-transform: rotate(45deg);
transform: rotate(45deg);
}Modern browsers (Chrome, Firefox, Safari, Edge) work without prefixes.
Reversible elements with 3D style in CSS

Over time, we have seen many things we can do with CSS; selectors, animations, and transitions are some of the many components often present in previous posts about CSS. This is largely due to their versatility, ease of use, and the great visual effect achieved; this entry will be no exception, and we will see how to make an element reversible by using CSS transitions and CSS animations as a key component for the reversibility of HTML elements:
Hover your cursor over the green container.
The base HTML code we will use to show all the examples is as follows:
<div class="reversible reversibleImagen"> <div id="atras"><span>1</span> </div> <div id="adelante"><span>2</span> </div> </div> We have two divs that we will call adelante (front) and atras (back) respectively, inside a block or another div called reversible; the general idea is to locate both internal divs (front and back) in the same position and rotate their parent (reversible) to show the divs from front to back and vice versa.
Before showing the CSS, we will explain the most important parts that must be part of the CSS to make an element reversible; let's see:
- The perspective property in CSS
- Perspective in CSS adds a depth factor (Z) to HTML elements, which is essential for correctly visualizing our example; although there is already a post where we talk about the perspective property in CSS in more detail: Understanding the perspective property in CSS; this property will be applied to the parent of the reversible element (which is the one that rotates).
- The transform-style property in CSS
- Determines whether the children of the assigned element can be seen in 3D space (preserve-3d) or not (flat).
- The backface-visibility property in CSS
- This property indicates whether an element seen from its reverse side (rotated 180 degrees) or -from behind- should be visible (by default) or not; for our example, we are interested in it not being visible because behind one element is another element (front and back).
Finally, the CSS:
.reversible{
transform-style : preserve-3d;
transition : transform 3s ease;
margin: 0 auto;
width : 200px;
height : 200px;
}
.reversible:hover{
transform : rotateY(180deg);
}
.reversible div{
position : absolute;
top : 300px;
width : 200px;
height : 200px;
backface-visibility : hidden;
border:3px solid #000000;
}
.reversible div span{
font-size: 180px;
display:block;
text-align:center;
color:#FFF;
}
.reversible div img{
width:200px;
height:200px;
object-fit: none;
}
.example1, .example2{
perspective : 500px;
}
#atras{
background : #00FF00;
transform : rotateY( 180deg );
}
#adelante{
background : #FF0000;
}The reason for these lines:
#atras{
transform : rotateY( 180deg );
}Is to show the back div, as a result of the effect that the backface-visibility property would have on the div:
Animations and reversible elements
As we mentioned at the beginning of this entry, it is also possible to use animations to create reversible elements like the following:
To do this, we use the following CSS code lines:
@-webkit-keyframes reverso {
0%{
transform : rotateY(0deg);
}
50%{
transform : rotateY(180deg);
}
100%{
transform : rotateY(0deg);
}
}
@keyframes reverso {
0%{
transform : rotateY(0deg);
}
100%{
transform : rotateY(180deg);
}
}
.reversibleImagen{
animation: reverso 5s infinite;
}Furthermore, getting a little more creative, we can add images to the internal divs (front and back) in conjunction with the object-fit property previously discussed in: The object-fit property in CSS to fit the image to the container:


Frequently Asked Questions
- How to rotate an element with CSS?
- With transform: rotate(angle);.
- How to do an infinite rotation?
- With a CSS animation (@keyframes rotate).
- How to rotate only the background image?
- Using a pseudoelement ::before or ::after.
- Why does my element distort when rotating?
- Lack of perspective in 3D rotations.
- Can I combine several transformations?
- Yes: transform: rotate(45deg) scale(1.2) translateX(10px);
Conclusion
CSS3 rotations are a powerful and flexible tool that you can use for animations, visual effects, and interactive components. From simple turns with rotate() to advanced rotations with rotate3d(), the key is to understand the origin (transform-origin) and how it influences the result.
When I started teaching this, I always recommended the same thing: start with 2D rotations, play with positive and negative values, and then try 3D with perspective. In a few minutes, you will have professional effects without a single line of JavaScript.
I agree to receive announcements of interest about this Blog.
Learn how to apply rotations with CSS3 in 2D and 3D: rotate(), rotateX(), rotateY(), flip cards, animated loaders, and techniques for rotating backgrounds with pseudo-elements. Practical guide and examples.