Transforms change how an element is drawn, moving, scaling or rotating it, without changing the layout around it. Neighbouring elements stay exactly where they were. That makes transforms ideal for hover effects and animation.
Transforms do not affect layout
.badge {
transform: translateY(-4px);
}
The badge is drawn 4px higher, but the space it occupies in the layout does not move. Compare this with margin-top: -4px, which would pull the following content up too.
Because the layout is unaffected, browsers can usually animate transforms without recalculating the page, which is why they are the preferred way to animate movement.
Transforms apply to block-level and atomic inline elements such as inline-block, flex items and images. They have no effect on plain inline elements like a <span> in a sentence.
translate
translate() moves an element:
.a { transform: translateX(2rem); } /* right */
.b { transform: translateY(-1rem); } /* up */
.c { transform: translate(2rem, -1rem); } /* both */
Percentages in translate() refer to the element's own size, not its parent's. translateX(100%) moves an element by exactly its own width, which is useful for sliding panels in from off-screen:
.drawer {
position: fixed;
inset-block: 0;
inset-inline-end: 0;
width: min(20rem, 90%);
transform: translateX(100%);
transition: transform 250ms ease-out;
}
.drawer.is-open {
transform: translateX(0);
}
The classic centring trick
You will often see this in existing code:
.modal {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
top and left put the element's top-left corner in the middle; the translate pulls it back by half its own size. It works, but Grid's place-items: center is usually simpler today.
scale
scale() makes an element larger or smaller. 1 is normal size:
.thumbnail:hover {
transform: scale(1.05);
}
.button:active {
transform: scale(0.97);
}
.flip {
transform: scaleX(-1); /* mirror horizontally */
}
Scaling scales everything, including text and borders. A small scale on hover is fine; scaling text up a lot can look blurry during the animation, as browsers may render it as an image while it moves.
rotate
.chevron {
transition: transform 200ms ease-out;
}
.disclosure[open] .chevron {
transform: rotate(180deg);
}
Angles can be written in deg, turn (one full rotation is 1turn), or rad. Positive values rotate clockwise.
skew
skew() slants an element along an axis:
.slanted {
transform: skewX(-10deg);
}
It is mostly used for decorative shapes. Skewing text reduces readability.
Combining transforms: order matters
You can list several functions in one transform. Read them from left to right: each one works in the coordinate system left behind by the one before it. So the order changes the result.
.one {
transform: translateX(10rem) rotate(45deg); /* moves right, then rotates in place */
}
.two {
transform: rotate(45deg) translateX(10rem); /* rotates first, then moves along the rotated axis: diagonally */
}
A second transform declaration replaces the first, it does not add to it:
.card {
transform: rotate(-2deg);
}
.card:hover {
transform: translateY(-4px); /* the rotation is lost */
}
To keep both, repeat them: transform: rotate(-2deg) translateY(-4px);.
Individual transform properties
Modern CSS has separate translate, rotate and scale properties that solve that problem:
.card {
rotate: -2deg;
transition: translate 200ms ease-out, scale 200ms ease-out;
}
.card:hover {
translate: 0 -4px;
scale: 1.02;
}
Each property can be set, overridden and transitioned independently, so the hover rule no longer wipes out the rotation. Note the syntax: values are space-separated (translate: 0 -4px), not functions.
They are always applied in a fixed order, translate then rotate then scale, followed by anything in the transform property. If you need a different order, use transform.
The individual transform properties are supported in all current major browsers. In very old browsers they are ignored, so the element simply does not move, which is usually an acceptable fallback for a hover effect.
transform-origin
Rotation and scaling happen around a point, the origin. By default it is the centre of the element:
.door {
transform-origin: left center;
transition: rotate 300ms ease-out;
}
.door:hover {
rotate: -8deg; /* swings around its left edge */
}
.underline {
transform: scaleX(0);
transform-origin: left;
transition: transform 250ms ease-out;
}
a:hover .underline {
transform: scaleX(1);
}
The underline grows from the left edge instead of from the middle. transform-origin accepts keywords (top, left, center), lengths and percentages.
A taste of 3D
Transforms also work in three dimensions. Add perspective to a parent so depth looks natural:
.scene {
perspective: 800px;
}
.card-3d {
transition: transform 400ms ease-out;
}
.scene:hover .card-3d {
transform: rotateY(15deg);
}
Use 3D effects sparingly and always under a reduced-motion check. They are among the effects most likely to cause discomfort.
Side effects to know about
- Any
transformother thannonecreates a stacking context. It can change how z-index works inside the element. - A transformed element becomes the containing block for
position: fixeddescendants, so a "fixed" child scrolls with it. - The clickable area moves with the drawn element, not with the layout space it left behind.
These effects explain many puzzling bugs. If adding a hover transform breaks a dropdown or a fixed element, this is why.
Reduced motion
@media (prefers-reduced-motion: reduce) {
.card:hover {
translate: none;
scale: none;
}
}
Small hover movements are usually fine for most people, but large slides, zooms, spins and 3D rotations should be removed or replaced with a fade when the user asks for reduced motion.
Debugging transforms
- Inspect the element: DevTools highlights the transformed box, while the layout space stays where it was.
- If a transform has no effect, check that the element is not a plain inline element.
- If one transform cancels another, switch to the individual properties.
- In the Animations panel, slow the transition down to check the origin and direction.
Summary
- Transforms change how an element is drawn without affecting layout around it.
translate()percentages refer to the element's own size;scale()uses1as normal;rotate()takes angles.- Functions in
transformapply in order, and a latertransformreplaces an earlier one. - The individual
translate,rotateandscaleproperties can be set and animated independently. transform-originsets the pivot point; transforms create stacking contexts and containing blocks.