← Back to CSS Course | Chapter 8: Animations & Effects | Lesson 6 of 22

CSS 2D Transforms

A 2D transform reshapes how an element is drawn on screen — moved, spun, resized, or tilted — without changing the space it takes up in the normal document flow, and without touching the HTML at all.
Syntax
css
selector {
  transform: translate(x, y) rotate(angle) scale(factor) skew(angle);
}

What Counts as a 2D Transform

A 2D transform operates purely within the flat X/Y plane of the screen: it can move, rotate, scale, or skew an element, but never gives it depth. All of these functions are passed as values of the single transform property, and several can be combined in one declaration.

Note:
  • Accepted values:
  • transform: none — default, no transform
  • translate(x, y) — moves, with px or %
  • rotate(angle) — deg, turn, or rad
  • scale(x, y) — number multipliers, 1 is unchanged
  • skew(x, y) — angles in deg
  • matrix(a, b, c, d, tx, ty) — combined 2D transform
  • transform-origin — keywords, lengths, or percentages
  • inherit | initial | unset | revert — CSS-wide keywords accepted by every property

Example: What Counts as a 2D Transform

css
<style>
.box {
  background: steelblue;
  color: white;
  padding: 10px;
  display: inline-block;
}
.box:hover {
  transform: translateX(20px) rotate(10deg) scale(1.1);
}
</style>
<div class="box">Move, rotate, and scale, all within the flat X/Y plane</div>

transform-origin

transform-origin sets the fixed point a rotation or scale pivots around, defaulting to the element's exact center (50% 50%). Changing it to a corner or edge makes a rotate() spin around that point instead, which matters a lot for hinge-like or corner-anchored animations.

Note:
  • Accepted values:
  • center — default, 50% 50%
  • top left | bottom right — keywords for corners
  • left | right | top | bottom | center — keyword positions
  • <length> <length> — offsets from the top-left, e.g. 10px 20px
  • <percentage> — relative to the element's size
  • <z-offset> — optional third length value for 3D
  • inherit | initial | unset | revert — CSS-wide keywords accepted by every property

Example: transform-origin

css
<style>
.box {
  transform-origin: top left;
  background: steelblue;
  color: white;
  padding: 10px;
  display: inline-block;
}
.box:hover {
  transform: rotate(30deg);
}
</style>
<div class="box">Pivots around its top-left corner instead of the center</div>

The matrix() Function

matrix(a, b, c, d, tx, ty) is the low-level mathematical form every other 2D transform function compiles down to internally — six numbers encoding scale, skew, and translation in one matrix. It's rarely written by hand, but understanding it explains why transform functions compose the way they do.

Note:
  • Accepted values:
  • matrix(a, b, c, d, tx, ty) — six numbers for a 2D affine transform
  • matrix(1, 0, 0, 1, 0, 0) — identity, no change
  • a and d — scale on x and y
  • b and c — skew (and rotation)
  • tx and ty — translation in px

Example: The matrix() Function

css
<style>
.box {
  background: steelblue;
  color: white;
  padding: 10px;
  display: inline-block;
}
.box:hover {
  transform: matrix(1, 0, 0, 1, 20, 10);
}
</style>
<div class="box">matrix() equivalent to translate(20px, 10px)</div>

Order of Combined Transforms

When multiple transform functions are listed in one declaration, they apply right-to-left in effect, meaning the order you write them changes the visual result — rotating then translating produces a different outcome than translating then rotating the same amounts.

Note:
  • Accepted values:
  • transform: translate(...) rotate(...) — moves along the normal axes, then rotates in place
  • transform: rotate(...) translate(...) — rotates first, so the move follows the rotated axis
  • multiple functions — read right to left when reasoning about the result
  • inherit | initial | unset | revert — CSS-wide keywords accepted by every property

Example: Order of Combined Transforms

css
<style>
.a {
  background: steelblue;
  color: white;
  padding: 10px;
  display: inline-block;
  margin: 30px;
}
.a:hover {
  transform: rotate(45deg) translate(50px);
}
.b {
  background: seagreen;
  color: white;
  padding: 10px;
  display: inline-block;
  margin: 30px;
}
.b:hover {
  transform: translate(50px) rotate(45deg);
}
</style>
<div class="a">Rotate then translate</div>
<div class="b">Translate then rotate — a different result</div>

2D Transforms and Layout

Because transforms operate after layout is calculated, a transformed element still reserves its original, untransformed space in the page flow — visually moving an element with translate() does not shift its neighboring elements, unlike changing margin or position would.

Note:
  • Accepted values:
  • transform — does not change layout; the original space is still reserved
  • translate — shifts visually without affecting siblings
  • scale — grows visually without pushing neighbors
  • width / margin — use these when layout should change
  • inherit | initial | unset | revert — CSS-wide keywords accepted by every property

Example: 2D Transforms and Layout

css
<style>
.moved {
  background: steelblue;
  color: white;
  padding: 10px;
  display: inline-block;
}
.moved:hover {
  transform: translateX(60px);
}
</style>
<div class="moved">Moved visually</div>
<div style="background: #ddd; padding: 10px; display: inline-block;">Neighbor does not shift — original space still reserved</div>
Live Example
Related Topics
{# common_mistakes/chapter_summary/browser_support: on Hindi pages the view already swaps in the hi_ translation fields (or blanks these out if untranslated), so this renders correctly for both languages without a lang_code check here. #}
Common Mistakes
  1. Rotating with rotate(45) and no unit, which is invalid; use 45deg.
  2. Writing transforms in the wrong order, since translate then rotate differs from rotate then translate.
  3. Expecting a transform to change the layout, when the space the element occupies stays the same.
Browser Support

Supported in all modern browsers: Chrome, Firefox, Safari, Edge and Opera.

Login to run this code

C/C++/Java/PHP execution requires a free account. Your code is saved — you'll land right back in the editor after logging in.