skip to content

In CSS, why does `transform: translateX(100px) rotate(45deg)` place an element differently from `transform: rotate(45deg) translateX(100px)`?

level: middleimportance: should knowfreq 50%

answer

  1. order is multiplication, not addition
  2. each function sees the previous axes
  3. rotate first tilts the axes
  4. origin pivots rotate, scale, skew — not translate
  5. keep the list shape identical across states

basics

~10 s

Transform functions apply left to right, each operating in the coordinate system the previous one left behind. Rotating first tilts the axes, so the following translateX moves along a diagonal instead of straight right.

solid answer

~40 s

A transform list is a sequence of matrix multiplications applied left to right, and each function works in the coordinate system produced by the ones before it. `translateX(100px) rotate(45deg)` moves the element 100px right along the page's own axes and then spins it in place around its `transform-origin`. `rotate(45deg) translateX(100px)` rotates the coordinate system first, so the subsequent `translateX` runs along the tilted X axis — the element ends up 100px away on a diagonal. That is why orbit effects are written rotate-then-translate, and why plain offsets are written translate-first. `transform-origin` (default `50% 50%`, the centre of the border box) sets the pivot for rotation, scale and skew, though it does not affect a pure translate. Keeping the function order identical across states also keeps transitions interpolating function-by-function.

go deeper

for a junior

Recall that the functions run left to right and that swapping them changes the result; be able to point out which of two lists moves the element straight sideways.

for a middle

Explain the coordinate-system chaining, name transform-origin and its 50% 50% default, and say why it has no effect on a pure translate.

for a senior

Bring in the animation consequence — matched lists interpolate per function, mismatched ones interpolate matrices — and describe the ordering convention you enforce so states stay compatible.

for a principal

Talk about the motion contract across a codebase: a canonical function order, whether the individual translate/rotate/scale properties become the house style, and what that costs in browser support and expressiveness.

## Transforms compose, they do not just accumulate A transform list is not a bag of independent adjustments. It is an ordered chain: each function is applied in the local coordinate system established by the function to its left. Mathematically the list is multiplied together into one matrix, and matrix multiplication is not commutative — swapping two functions generally gives a different result. The practical reading is: **imagine dragging the element's own axes along with it**. - `translateX(100px) rotate(45deg)` — first slide 100px right along the page's horizontal axis, then rotate around the element's origin, which has moved with it. Net effect: element sits 100px right, tilted 45°. - `rotate(45deg) translateX(100px)` — first tilt the coordinate system 45°, then move 100px along the *now-diagonal* X axis. Net effect: element sits roughly 71px right and 71px down, tilted 45°. This is why radial menus and clock hands are written rotate-first: `rotate(θ) translateY(-radius)` sweeps an item around a circle as θ changes, because the translate always runs along the rotated axis. Writing the two the other way round would move every item to the same place and merely spin it. ```css /* nudge then tilt: ends 100px to the right */ .badge { transform: translateX(100px) rotate(45deg); } /* tilt then travel: ends on a diagonal */ .orbiting { transform: rotate(45deg) translateX(100px); } ``` ## `transform-origin`: where the pivot sits `transform-origin` sets the point that rotation, scaling and skewing act around. Its initial value is `50% 50%` — the centre of the element's border box. Change it and the whole character of the transform changes: ```css .door { transform-origin: left center; transform: rotateY(-80deg); } .grow-from-corner { transform-origin: 0 0; transform: scale(1.4); } ``` Two things people get wrong about it: - **It does nothing for a pure `translate()`.** Sliding a box by a vector produces the same result no matter which point you nominate as the origin. If changing `transform-origin` appears to have no effect, check whether the transform contains anything but a translate. - **It accepts lengths, percentages and keywords**, and a third value for the Z axis (`transform-origin: 50% 50% -100px`). Percentages resolve against the element's own box, like translate percentages do. ## Everything collapses to one matrix Whatever you write, the result is a single matrix. `matrix(a, b, c, d, e, f)` is the 2D form — the six values of an affine transform — and `matrix3d()` takes sixteen for the 3D case. You will rarely author these by hand; they matter because the resolved value of `transform` is expressed that way, so tooling and inspectors show you a matrix rather than the friendly functions you typed. This also explains a subtle animation behaviour. When two states use transform lists with the **same functions in the same order**, the browser interpolates each function against its counterpart — a rotation animates as a rotation, a scale as a scale. When the lists **do not match**, the browser falls back to interpolating the matrices, which decomposes and re-composes them; the endpoints are still correct, but the path between them can look different from what you expected, and large rotations in particular can take an unintuitive route. ```css /* matched lists — each function interpolates cleanly */ .card { transform: translateY(0) scale(1); } .card:hover { transform: translateY(-4px) scale(1.03); } ``` The practical habit: pick a canonical order — commonly `translate() rotate() scale()` — and write every state of the same element with all of those functions present, using identity values (`translateY(0)`, `scale(1)`) rather than omitting them. ## The individual properties sidestep the ordering problem CSS Transforms Level 2 adds `translate`, `rotate` and `scale` as standalone properties, available in current Chromium, Firefox and Safari: ```css .card { translate: 0 0; rotate: 0deg; scale: 1; transition: translate 200ms, rotate 200ms, scale 200ms; } .card:hover { rotate: 3deg; scale: 1.05; } ``` Because they are separate properties, one rule can change the rotation without having to restate the translation, and each can transition with its own duration — something a single `transform` shorthand cannot do. Their composition order is fixed by the specification: `translate`, then `rotate`, then `scale`, and finally whatever is in `transform`. That fixed order is a feature (no accidental reordering) and a constraint (you cannot express rotate-then-translate orbits with them, so orbit effects still want the `transform` list).

  • How would you make an item orbit around a centre point using transforms?
    Rotate first, then translate outward: `transform: rotate(var(--angle)) translateY(calc(-1 * var(--radius)))`. Because the translate runs along the already-rotated axis, changing the angle sweeps the item around a circle of that radius. Add a counter-rotation on an inner wrapper if you want the item itself to stay upright while it orbits.
  • Why should both states of a transform transition list the same functions, even the ones that do not change?
    Matching lists interpolate function by function, so a rotation animates as a rotation. If the lists differ, the browser interpolates decomposed matrices instead, and the in-between frames can follow a visibly different path. Writing identity values such as `translateY(0)` or `scale(1)` in the base state keeps the lists matched.
  • What can the individual `rotate` and `scale` properties do that a single `transform` shorthand cannot?
    They are separate properties, so different rules can change one without restating the others, and each can carry its own transition duration and timing function. The tradeoff is a fixed composition order — translate, then rotate, then scale, then `transform` — so effects that need rotate-before-translate still belong in the shorthand.

Walking ten paces then turning ends somewhere different from turning then walking ten paces — the turn changes which way "forward" points.

saying these in an interview costs you the question

  • Says transform functions are applied right to left
  • Claims the order does not matter because they are all applied at once
  • Thinks transform-origin shifts a pure translate
  • Believes transform-origin defaults to the top-left corner
  • Says mismatched transform lists cannot animate at all

context