CSS Transforms
The CSS transformproperty modifies the coordinate space of an element, enabling you to translate (move), rotate, scale, and skew it in 2D or 3D space. Transforms do not affect the layout of surrounding elements — the element's original space is preserved — making them ideal for animations and visual effects without triggering layout recalculations.
All transform functions are compositor-only properties, meaning they can be animated at 60fps without triggering layout or paint. This makes them the foundation of performant CSS animations.
| 1 | .element { |
| 2 | transform: translate(20px, 10px) rotate(45deg) scale(1.2); |
| 3 | /* Multiple functions are chained — applied right to left */ |
| 4 | transform-origin: center; /* default transform origin */ |
| 5 | } |
The transformproperty accepts one or more transform functions separated by spaces. Functions are applied right to left (last function is applied first to the element's coordinate space).
| 1 | .element { |
| 2 | /* Single function */ |
| 3 | transform: rotate(45deg); |
| 4 | |
| 5 | /* Multiple functions — chained */ |
| 6 | transform: translateX(40px) rotate(45deg) scale(1.5); |
| 7 | |
| 8 | /* No transform (default) */ |
| 9 | transform: none; |
| 10 | } |
| 11 | |
| 12 | /* Common transform functions */ |
| 13 | /* |
| 14 | translate(x, y) — move by x and y |
| 15 | translateX(x) — move horizontally |
| 16 | translateY(y) — move vertically |
| 17 | rotate(angle) — rotate around Z axis |
| 18 | scale(x, y) — scale horizontally and vertically |
| 19 | scaleX(x) — scale horizontally |
| 20 | scaleY(y) — scale vertically |
| 21 | skew(x-angle, y-angle) — skew along axes |
| 22 | matrix(a, b, c, d, e, f) — 2D transformation matrix |
| 23 | */ |
The translate() function moves an element from its current position by given x and y offsets. Unlike using top and left, translating does not trigger layout — the element's original space is preserved.
| 1 | .move-right { |
| 2 | transform: translateX(40px); /* move 40px right */ |
| 3 | } |
| 4 | |
| 5 | .move-down { |
| 6 | transform: translateY(20px); /* move 20px down */ |
| 7 | } |
| 8 | |
| 9 | .move-both { |
| 10 | transform: translate(40px, 20px); /* 40px right, 20px down */ |
| 11 | } |
| 12 | |
| 13 | /* Percentage is relative to the element's own size */ |
| 14 | .centered { |
| 15 | position: absolute; |
| 16 | top: 50%; |
| 17 | left: 50%; |
| 18 | transform: translate(-50%, -50%); |
| 19 | /* perfectly centered regardless of element size */ |
| 20 | } |
The rotate() function rotates the element around its transform-origin point by a given angle. Angles can be specified in deg, rad, grad, or turn.
| 1 | .rotate-45 { |
| 2 | transform: rotate(45deg); /* 45 degrees clockwise */ |
| 3 | } |
| 4 | |
| 5 | .rotate-neg { |
| 6 | transform: rotate(-90deg); /* 90 degrees counter-clockwise */ |
| 7 | } |
| 8 | |
| 9 | .rotate-turn { |
| 10 | transform: rotate(0.25turn); /* quarter turn = 90deg */ |
| 11 | } |
| 12 | |
| 13 | .rotate-rad { |
| 14 | transform: rotate(1.5708rad); /* pi/2 = 90deg */ |
| 15 | } |
The scale() function resizes the element by multiplying its dimensions by a factor. A value of 1 means no scaling, less than 1 shrinks, greater than 1 grows. Negative values flip the element.
| 1 | .uniform-scale { |
| 2 | transform: scale(1.5); /* 1.5x larger in both axes */ |
| 3 | } |
| 4 | |
| 5 | .shrink { |
| 6 | transform: scale(0.75); /* 75% of original size */ |
| 7 | } |
| 8 | |
| 9 | .x-only { |
| 10 | transform: scaleX(2); /* stretch horizontally only */ |
| 11 | } |
| 12 | |
| 13 | .y-only { |
| 14 | transform: scaleY(1.5); /* stretch vertically only */ |
| 15 | } |
| 16 | |
| 17 | /* Negative values flip */ |
| 18 | .flip-horizontal { |
| 19 | transform: scaleX(-1); /* mirror horizontally */ |
| 20 | } |
| 21 | |
| 22 | .flip-vertical { |
| 23 | transform: scaleY(-1); /* mirror vertically */ |
| 24 | } |
The skew()function slants the element along the X and Y axes. Each angle specifies how much the element is skewed — positive values skew toward the positive direction of the axis. Skewing distorts the element's shape, turning rectangles into parallelograms.
| 1 | .skew-x { |
| 2 | transform: skewX(10deg); /* slant horizontally */ |
| 3 | } |
| 4 | |
| 5 | .skew-y { |
| 6 | transform: skewY(15deg); /* slant vertically */ |
| 7 | } |
| 8 | |
| 9 | .skew-both { |
| 10 | transform: skew(10deg, 15deg); /* slant both axes */ |
| 11 | } |
| 12 | |
| 13 | .single-value { |
| 14 | transform: skew(10deg); /* only X axis, Y defaults to 0 */ |
| 15 | } |
The matrix() function combines all 2D transforms into a single 6-value transformation matrix: matrix(scaleX, skewY, skewX, scaleY, translateX, translateY). It is the mathematical foundation that all other transform functions compile down to.
| 1 | .matrix-transform { |
| 2 | /* matrix(scaleX, skewY, skewX, scaleY, translateX, translateY) */ |
| 3 | transform: matrix(1, 0.5, 0.5, 1, 20, 10); |
| 4 | } |
| 5 | |
| 6 | /* Equivalent to: translate(20px, 10px) skew(atan(0.5)deg) */ |
| 7 | /* Usually you don't write matrix() directly — |
| 8 | the browser generates it internally when you chain functions */ |
info
The transform-origin property changes the pivot point for transforms. By default, transforms are applied from the center of the element (50% 50%). Changing the origin is essential for effects like rotating from a corner or scaling from a specific edge.
| 1 | .origin-center { |
| 2 | transform-origin: center; /* default — 50% 50% */ |
| 3 | } |
| 4 | |
| 5 | .origin-top-left { |
| 6 | transform-origin: top left; /* 0 0 */ |
| 7 | } |
| 8 | |
| 9 | .origin-top-right { |
| 10 | transform-origin: top right; /* 100% 0 */ |
| 11 | } |
| 12 | |
| 13 | .origin-custom { |
| 14 | transform-origin: 20px 50%; /* custom position */ |
| 15 | } |
| 16 | |
| 17 | /* transform-origin affects rotation direction */ |
| 18 | .rotate-center { |
| 19 | transform-origin: center; |
| 20 | transform: rotate(45deg); |
| 21 | } |
| 22 | |
| 23 | .rotate-corner { |
| 24 | transform-origin: top left; |
| 25 | transform: rotate(45deg); /* rotates around top-left corner */ |
| 26 | } |
Multiple transform functions can be chained in a single declaration. Each function is applied in order from right to left— the last function is applied to the element's original coordinate space first, then subsequent functions build on the result. This means the order of functions matters.
| 1 | .chained-1 { |
| 2 | /* translate first, then rotate */ |
| 3 | transform: translate(40px, 0) rotate(45deg); |
| 4 | /* Moves 40px right, then rotates around new center */ |
| 5 | } |
| 6 | |
| 7 | .chained-2 { |
| 8 | /* rotate first, then translate */ |
| 9 | transform: rotate(45deg) translate(40px, 0); |
| 10 | /* Rotates first, then moves 40px along rotated X axis */ |
| 11 | /* This produces a completely different result! */ |
| 12 | } |
| 13 | |
| 14 | /* Common pattern: scale on hover */ |
| 15 | .card { |
| 16 | transition: transform 0.3s ease; |
| 17 | } |
| 18 | |
| 19 | .card:hover { |
| 20 | transform: translateY(-8px) scale(1.02); |
| 21 | } |
3D transforms extend the 2D coordinate system with a Z-axis. They require perspective to create depth, and transform-style: preserve-3d to maintain the 3D space for child elements.
perspective
The perspective property defines how far the viewer is from the Z=0 plane. Smaller values create more dramatic 3D effects; larger values create subtler depth. Values typically range from 200px to 1000px.
| 1 | .stage { |
| 2 | perspective: 800px; |
| 3 | /* Children with 3D transforms will appear in perspective */ |
| 4 | } |
| 5 | |
| 6 | /* Alternatively, use perspective as a transform function */ |
| 7 | .element { |
| 8 | transform: perspective(800px) rotateY(45deg); |
| 9 | } |
| 10 | |
| 11 | /* perspective-origin — changes the vanishing point */ |
| 12 | .stage { |
| 13 | perspective: 800px; |
| 14 | perspective-origin: center; /* default */ |
| 15 | } |
| 16 | |
| 17 | .stage-left { |
| 18 | perspective: 800px; |
| 19 | perspective-origin: left center; |
| 20 | } |
rotateX, rotateY, rotateZ
| 1 | .rotate-x { |
| 2 | transform: rotateX(45deg); /* tilt forward/backward */ |
| 3 | } |
| 4 | |
| 5 | .rotate-y { |
| 6 | transform: rotateY(45deg); /* rotate around vertical axis */ |
| 7 | } |
| 8 | |
| 9 | .rotate-z { |
| 10 | transform: rotateZ(45deg); /* same as rotate() — spin in plane */ |
| 11 | } |
| 12 | |
| 13 | /* Card flip example */ |
| 14 | .card-container { |
| 15 | perspective: 1000px; |
| 16 | } |
| 17 | |
| 18 | .card { |
| 19 | width: 200px; |
| 20 | height: 280px; |
| 21 | transform-style: preserve-3d; |
| 22 | transition: transform 0.6s; |
| 23 | } |
| 24 | |
| 25 | .card:hover { |
| 26 | transform: rotateY(180deg); |
| 27 | } |
| 28 | |
| 29 | .card-front, .card-back { |
| 30 | position: absolute; |
| 31 | inset: 0; |
| 32 | backface-visibility: hidden; |
| 33 | } |
| 34 | |
| 35 | .card-back { |
| 36 | transform: rotateY(180deg); |
| 37 | } |
translateZ
translateZ() moves the element closer to or further from the viewer along the Z-axis. Positive values bring it closer (making it appear larger), negative values push it away.
| 1 | .closer { |
| 2 | transform: perspective(500px) translateZ(100px); |
| 3 | /* appears larger — closer to viewer */ |
| 4 | } |
| 5 | |
| 6 | .further { |
| 7 | transform: perspective(500px) translateZ(-100px); |
| 8 | /* appears smaller — further from viewer */ |
| 9 | } |
| 10 | |
| 11 | /* transform-style — preserve 3D space */ |
| 12 | .stage { |
| 13 | perspective: 800px; |
| 14 | transform-style: preserve-3d; |
| 15 | } |
| 16 | |
| 17 | /* Without preserve-3d, children are flattened into the parent's plane */ |
| 18 | .stage-flat { |
| 19 | perspective: 800px; |
| 20 | transform-style: flat; /* default — flattens children */ |
| 21 | } |
Transform operations are handled entirely by the GPU compositor thread, meaning they skip the layout and paint stages of the rendering pipeline. This makes them the most performant way to animate elements — consistently achieving 60fps even on low-powered devices.
| 1 | .animate-me { |
| 2 | /* These trigger ONLY compositing — 60fps safe */ |
| 3 | transform: translateX(100px); |
| 4 | transform: scale(1.5); |
| 5 | transform: rotate(45deg); |
| 6 | opacity: 0.5; |
| 7 | |
| 8 | /* These trigger LAYOUT — avoid for animation */ |
| 9 | /* width: 200px; */ |
| 10 | /* height: 200px; */ |
| 11 | /* top: 50px; */ |
| 12 | /* left: 100px; */ |
| 13 | } |
| 14 | |
| 15 | /* will-change — hint to promote to GPU layer */ |
| 16 | .will-animate { |
| 17 | will-change: transform, opacity; |
| 18 | /* Use sparingly — creates compositor layers */ |
| 19 | } |
| 20 | |
| 21 | /* Legacy GPU promotion (avoid in modern browsers) */ |
| 22 | .gpu { |
| 23 | transform: translateZ(0); |
| 24 | /* Forces GPU compositing for the element */ |
| 25 | } |
| Property | Triggers Layout | Triggers Paint | Triggers Composite |
|---|---|---|---|
| transform | ✗ | ✗ | ✓ |
| opacity | ✗ | ✗ | ✓ |
| top / left | ✓ | ✓ | ✓ |
| width / height | ✓ | ✓ | ✓ |
best practice