CSS transform lets you move, rotate, resize, and distort elements visually, without affecting the document layout around them. It is one of the most important CSS properties for building modern UIs, and because it is GPU-accelerated, it is also one of the most performant ways to animate elements. This guide covers every transform function, 2D and 3D, how to combine them with transitions and Tailwind CSS, and how to avoid the common mistakes.

CSS transform functions: quick reference

Every transform function at a glance. Each one is covered in detail further down the page with live code examples.

FunctionEffectExample
translate()Moves along X and Ytranslate(50px, 20px)
translateX()Moves horizontallytranslateX(50px)
translateY()Moves verticallytranslateY(20px)
translateZ()Moves along the Z axis (3D)translateZ(100px)
translate3d()Moves along X, Y, and Ztranslate3d(50px, 20px, 100px)
rotate()Rotates in 2Drotate(45deg)
rotateX()Rotates around the horizontal axisrotateX(45deg)
rotateY()Rotates around the vertical axisrotateY(45deg)
rotateZ()Rotates around the Z axis, same as rotate()rotateZ(45deg)
rotate3d()Rotates around a custom 3D vectorrotate3d(1, 1, 0, 45deg)
scale()Resizes on X and Yscale(1.5)
scaleX()Resizes horizontally onlyscaleX(1.5)
scaleY()Resizes vertically onlyscaleY(0.5)
scale3d()Resizes on X, Y, and Zscale3d(1.2, 1.2, 1)
skew()Slants on X and Yskew(20deg, 10deg)
skewX()Slants horizontallyskewX(20deg)
skewY()Slants verticallyskewY(10deg)
matrix()2D transform shorthand (6 values)matrix(1, 0, 0, 1, 50, 20)
matrix3d()3D transform shorthand (16 values)matrix3d(1,0,0,0, 0,1,0,0, 0,0,1,0, 50,20,0,1)
perspective()Sets 3D depth as a function valueperspective(800px)

How CSS transform works

The transform property applies a visual transformation to an element using one or more transform functions. The key thing to understand is that transform does not affect layout: the element still occupies its original space in the document. Neighbouring elements do not move or reflow when you transform something.

/* Basic syntax */
.element {
  transform: function(value);
}

/* Example */
.box {
  transform: translateX(50px);
}

Transforms are applied relative to the element's transform origin, which defaults to the centre of the element (50% 50%). You can change this with the transform-origin property.

translate: move an element

translate moves an element along the X and Y axes. Unlike changing top, left, or margin, translating an element does not trigger a layout recalculation, making it the correct way to move elements in animations.

/* Move right 50px */
transform: translateX(50px);

/* Move down 20px */
transform: translateY(20px);

/* Move right 50px and down 20px */
transform: translate(50px, 20px);

/* Percentages are relative to the element's own size */
transform: translate(50%, -50%);

/* 3D translate */
transform: translateZ(100px);
transform: translate3d(50px, 20px, 100px);

translate accepts any CSS length unit, not just pixels. rem and em scale with font size, while vh and vw scale with the viewport, which is useful for full-screen slide-in panels:

/* rem: scales with the root font size */
transform: translateX(2rem);

/* vh/vw: scales with the viewport, good for full-screen drawers */
transform: translateY(100vh);
transform: translateX(-100vw);

Percentage values in translate are relative to the element itself, not the parent. This makes translate(-50%, -50%) the standard technique for centering an absolutely positioned element:

.centered {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}

rotate: spin an element

rotate turns an element clockwise (positive values) or counter-clockwise (negative values) around its transform origin. The angle can be specified in degrees (deg), radians (rad), gradians (grad), or turns (turn).

/* Rotate 45 degrees clockwise */
transform: rotate(45deg);

/* Rotate 90 degrees counter-clockwise */
transform: rotate(-90deg);

/* One full rotation */
transform: rotate(1turn);

/* 3D rotations */
transform: rotateX(45deg); /* tilts toward/away from viewer */
transform: rotateY(45deg); /* spins around vertical axis */
transform: rotateZ(45deg); /* same as rotate() in 2D */

The turn unit is the most readable for full or partial rotations: 0.25turn is 90 degrees, 0.5turn is 180 degrees. This makes animation keyframes easier to reason about than degree values.

/* Spinning loader animation */
@keyframes spin {
  from { transform: rotate(0turn); }
  to   { transform: rotate(1turn); }
}

.spinner {
  animation: spin 1s linear infinite;
}

scale: resize an element

scale enlarges or shrinks an element around its transform origin. A value of 1 is the original size, above 1 is larger, and below 1 (but above 0) is smaller. Negative values flip the element.

/* Scale up to 150% */
transform: scale(1.5);

/* Scale down to 80% */
transform: scale(0.8);

/* Scale X and Y independently */
transform: scaleX(1.5); /* wider only */
transform: scaleY(0.5); /* shorter only */

/* Different X and Y values */
transform: scale(1.5, 0.8);

/* Flip horizontally (mirror) */
transform: scaleX(-1);

/* Flip vertically */
transform: scaleY(-1);

Unlike changing width and height, scale does not affect layout or trigger reflow: it is purely a visual scaling. This makes it ideal for hover effects and entrance animations.

/* Common hover scale effect */
.card {
  transition: transform 0.2s ease;
}

.card:hover {
  transform: scale(1.03);
}

skew: distort an element

skew tilts an element along the X or Y axis, creating a slanted or parallelogram-like distortion. It is less commonly used than the other transforms but is handy for decorative shapes and diagonal section dividers.

/* Skew along X axis (horizontal slant) */
transform: skewX(20deg);

/* Skew along Y axis (vertical slant) */
transform: skewY(10deg);

/* Skew both axes */
transform: skew(20deg, 10deg);

A common pattern is to skew a container for a diagonal effect and then counter-skew its child to keep the content straight:

.diagonal-section {
  transform: skewY(-4deg);
}

.diagonal-section .content {
  transform: skewY(4deg); /* undo the skew for inner content */
}

matrix() and matrix3d(): the low-level shorthand

matrix() is a single function that can represent any 2D combination of translate, rotate, scale, and skew as six numbers. You rarely write it by hand: it is what the browser produces internally, and what you see in DevTools or in the output of getComputedStyle() when you inspect an element with a transform applied.

/* matrix(scaleX, skewY, skewX, scaleY, translateX, translateY) */
transform: matrix(1, 0, 0, 1, 50, 20); /* same as translate(50px, 20px) */

/* scale(1.5) combined with translate(20px, 0) */
transform: matrix(1.5, 0, 0, 1.5, 20, 0);

The six values map to a 2D transformation matrix in this order: scaleX, skewY, skewX, scaleY, translateX, translateY. For 3D transforms, matrix3d() takes 16 values describing a full 4x4 matrix:

/* matrix3d: the identity matrix, i.e. no transform at all */
transform: matrix3d(
  1, 0, 0, 0,
  0, 1, 0, 0,
  0, 0, 1, 0,
  0, 0, 0, 1
);

When to actually use matrix(). In day-to-day CSS, prefer the named functions (translate, rotate, scale, skew): they are far more readable and easier to animate individually. matrix() is mostly useful when a JavaScript animation library needs to output a single transform value, or when you are reading a computed style and need to decode what transform is currently applied.

Combining multiple transforms

You can apply multiple transform functions in a single transform declaration by separating them with spaces. The order matters: transforms are applied from right to left, so the last function listed is applied first.

/* Rotate AND move AND scale: applied right to left */
transform: translateX(100px) rotate(45deg) scale(1.2);

/* Common hover: lift and scale */
.card:hover {
  transform: translateY(-4px) scale(1.02);
}

The order-dependency is a common source of bugs. translate(50px) rotate(45deg) produces a different result than rotate(45deg) translate(50px), because rotating first changes the direction of the subsequent translation.

/* Move right 50px, then rotate 45deg around new origin */
transform: rotate(45deg) translate(50px, 0);

/* Rotate 45deg first, then move 50px along rotated X axis */
transform: translate(50px, 0) rotate(45deg);

Do not use multiple separate transform declarations. The second one overwrites the first entirely. Only the last declaration applies.

/* WRONG: second transform overwrites the first */
.element {
  transform: translateY(-4px);
  transform: scale(1.02); /* this is all that applies */
}

/* CORRECT: combine in one declaration */
.element {
  transform: translateY(-4px) scale(1.02);
}

transform-origin: changing the pivot point

By default, all transforms happen around the element's centre (50% 50%). transform-origin lets you move that pivot point anywhere, which changes how rotations and scales behave visually.

/* Default: centre */
transform-origin: 50% 50%;
transform-origin: center;

/* Top-left corner */
transform-origin: 0 0;
transform-origin: top left;

/* Bottom-right corner */
transform-origin: 100% 100%;
transform-origin: bottom right;

/* Custom point */
transform-origin: 20px 80px;

/* 3D origin */
transform-origin: 50% 50% 100px;

Here is a practical example of a folding card that rotates from its top edge:

.flap {
  transform-origin: top center;
  transition: transform 0.4s ease;
}

.flap.open {
  transform: rotateX(-90deg);
}

3D transforms in depth

Beyond the individual translateZ, rotateX/Y/Z, and translate3d functions covered above, CSS gives you a few more tools for building genuine 3D scenes: rotate3d(), scale3d(), transform-style, and backface-visibility.

/* rotate3d(x, y, z, angle): rotate around a custom vector */
transform: rotate3d(1, 1, 0, 45deg); /* rotates around a diagonal axis */

/* scale3d(x, y, z): resize independently on all three axes */
transform: scale3d(1.2, 1.2, 1);

3D transforms only look three-dimensional when a perspective is set on the parent, and when children are allowed to exist in 3D space with transform-style: preserve-3d. Without preserve-3d, the browser flattens nested 3D transforms onto a single 2D plane.

.scene {
  perspective: 800px; /* how far the viewer is from the z=0 plane */
}

.card-inner {
  transform-style: preserve-3d; /* let children keep their own 3D position */
  transition: transform 0.6s;
}

.card.flipped .card-inner {
  transform: rotateY(180deg);
}

A classic use of 3D transforms is a flip card, where the front and back faces sit on top of each other and rotate to reveal one another. backface-visibility: hidden hides a face once it has rotated past 90 degrees, so you never see the mirrored back of the front face:

.card-face {
  position: absolute;
  inset: 0;
  backface-visibility: hidden;
}

.card-back {
  transform: rotateY(180deg); /* pre-rotated so it faces away initially */
}

Common mistake: setting perspective directly on the element you are rotating instead of on its parent. perspective only creates a 3D viewing context for the element's children, so it has no visible effect when applied to the rotating element itself.

Browser support and vendor prefixes

transform, transform-origin, and 3D transforms have shipped without vendor prefixes in every major browser since around 2016 (Chrome, Firefox, Edge, and Safari). You do not need -webkit-transform, -moz-transform, or -ms-transform for any browser still receiving updates today.

If you are maintaining a very old codebase or see -webkit-transform in a legacy tutorial, it is safe to remove: unprefixed transform now covers the same browsers the prefix used to target. The only prefix still worth knowing is -webkit-backface-visibility, which some older iOS Safari versions in the wild still benefit from alongside the standard property.

Combining transform with transition

transform has no built-in motion of its own: it jumps instantly from one value to another. To animate a transform smoothly, pair it with the transition property. This is the pattern behind almost every hover, press, and toggle effect on the modern web.

.button {
  transition: transform 0.2s ease;
}

.button:hover {
  transform: translateY(-2px) scale(1.03);
}

You can transition the transform property alone, or list it alongside other properties like box-shadow or background-color so everything animates together:

/* Transition transform only */
transition: transform 0.3s ease-out;

/* Transition transform plus other properties, each with its own timing */
transition: transform 0.3s ease-out, opacity 0.3s ease, box-shadow 0.2s ease;

/* Transition every animatable property that changes (less precise, but concise) */
transition: all 0.3s ease;

Because a single transform declaration can combine translate, rotate, and scale, a single transition: transform line animates all of them together automatically, even when several transform functions change at once between states.

For the full syntax of transition, including timing functions, delays, and animating properties like display, see the CSS Transition Guide. If you need multi-step animations with several keyframes instead of a simple two-state change, reach for @keyframes and animation, covered in the CSS Animation Guide.

CSS transform in Tailwind CSS

Tailwind CSS maps its transform utilities directly to the CSS functions covered in this guide. As of Tailwind v3 and v4, you no longer need to add a separate transform class to activate them; utilities like translate-x-4 or rotate-45 apply the transform on their own.

<!-- translate, rotate, and scale using Tailwind utilities -->
<div class="translate-x-4 translate-y-2">...</div>
<div class="rotate-45">...</div>
<div class="scale-110 hover:scale-125 transition-transform duration-200">...</div>
<div class="skew-y-6">...</div>

/* Negative values use a leading dash instead of a minus sign */
<div class="-translate-x-4 -rotate-12">...</div>

/* Arbitrary values in square brackets for anything off the default scale */
<div class="rotate-[17deg] translate-x-[2.5rem]">...</div>

Multiple Tailwind transform utilities combine automatically under the hood using CSS custom properties, so translate-x-4 rotate-45 scale-110 on the same element behaves like one combined transform declaration, in the same right-to-left order discussed above.

3D transforms have dedicated utilities too: transform-3d sets transform-style: preserve-3d on a parent, perspective-{value} sets perspective, and rotate-x-*, rotate-y-*, translate-z-*, and scale-z-* map to their 3D CSS equivalents.

<!-- Pairing a transform with a transition utility -->
<div class="hover:scale-110 hover:rotate-3 transition-transform duration-300">...</div>

Performance: why transform is the right choice for animation

Browsers render pages in layers. Animating properties like top, left, width, or margin forces the browser to recalculate the layout of the entire page on every animation frame. This is called reflow, and it is expensive.

transform and opacity are the two CSS properties that bypass reflow entirely. The browser hands them off to the GPU, which composites the element's layer independently. The result is smooth 60fps animation even on lower-end devices.

/* Slow: causes reflow on every frame */
@keyframes move-bad {
  from { left: 0; }
  to   { left: 200px; }
}

/* Fast: GPU composited, no reflow */
@keyframes move-good {
  from { transform: translateX(0); }
  to   { transform: translateX(200px); }
}

If you notice jank during a CSS animation, the first thing to check is whether you are animating a layout property instead of transform. Swapping left for translateX is often all it takes to fix dropped frames.

Practical examples

Card hover lift

.card {
  transition: transform 0.25s ease, box-shadow 0.25s ease;
}

.card:hover {
  transform: translateY(-6px);
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.15);
}

Button press effect

.btn {
  transition: transform 0.1s ease;
}

.btn:active {
  transform: scale(0.96);
}

Icon rotation on accordion toggle

.icon {
  transition: transform 0.3s ease;
}

.accordion.open .icon {
  transform: rotate(180deg);
}

3D flip card

.flip-card {
  perspective: 1000px;
}

.flip-card-inner {
  position: relative;
  transform-style: preserve-3d;
  transition: transform 0.6s;
}

.flip-card:hover .flip-card-inner {
  transform: rotateY(180deg);
}

.flip-card-front, .flip-card-back {
  position: absolute;
  inset: 0;
  backface-visibility: hidden;
}

.flip-card-back {
  transform: rotateY(180deg);
}

Entrance animation

@keyframes slide-up {
  from {
    opacity: 0;
    transform: translateY(24px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.hero-text {
  animation: slide-up 0.5s ease-out both;
}

FAQ

Does CSS transform affect layout?

No. transform is purely visual: the element still occupies its original space in the document flow. Neighbouring elements do not shift when you translate, rotate, or scale something. If you need the layout to respond to a size change, you must change actual size properties like width or margin.

Why does my second transform override the first?

Because transform is a single CSS property. Writing two separate transform declarations means the second one completely replaces the first. Always combine multiple transform functions in one declaration: transform: translateY(-4px) scale(1.02);

Does the order of transform functions matter?

Yes, significantly. Transforms are applied from right to left (last to first). rotate(45deg) translateX(100px) and translateX(100px) rotate(45deg) produce different visual results because rotation changes the axis direction of the subsequent translation.

What is the difference between translateX and left or margin-left?

translateX moves the element visually without affecting layout or triggering reflow. left and margin-left change the actual position in the document, which forces the browser to recalculate the layout of surrounding elements. Always use translateX for animations.

Can I use CSS transform in 3D?

Yes. CSS supports 3D transforms using translateZ, rotateX, rotateY, rotateZ, perspective, and translate3d. To enable 3D perspective, set perspective on the parent container. Without a perspective value, 3D transforms appear flat.

Why is my rotated element getting clipped?

If a rotated element is clipped, the parent likely has overflow: hidden. Because transform does not affect layout, the browser considers the element's bounding box to still be its original untransformed size, and overflow clipping is applied based on that. Remove overflow: hidden from the parent or add enough padding to accommodate the rotation.

What is the difference between transform: matrix() and functions like translate() or rotate()?

matrix() and matrix3d() are low-level representations that can express any combination of translate, rotate, scale, and skew as a set of numbers. Named functions like translate() and rotate() do the same math but stay readable and easy to animate individually. Browsers convert named functions to a matrix internally, so matrix() is mostly useful for reading computed styles or for JavaScript animation libraries, not for hand-written CSS.

How do I make CSS transform animate smoothly instead of jumping instantly?

transform has no built-in motion; it changes instantly unless paired with the transition property, for example transition: transform 0.3s ease;. Because a single transform declaration can combine translate, rotate, and scale, one transition: transform line animates all of them together whenever any of them changes.

Do I need vendor prefixes like -webkit-transform for CSS transform?

No. transform, transform-origin, and 3D transforms have shipped without vendor prefixes in every major browser since around 2016. -webkit-transform, -moz-transform, and -ms-transform are safe to remove from any codebase that only needs to support browsers still receiving updates.

Generate CSS animations visually

Our CSS Animation Generator lets you build keyframe animations using transform properties in real time and copy the exact code into your project.

Open Animation Generator →