Individual Transform Properties vs the transform Shorthand

Part of Motion Path & Individual Transforms in Core CSS Animation Fundamentals.

The problem

A card has an entrance animation that lifts it into place, and a hover state that lifts it a little further. Both are expressed as transform, so the second one erases the first: hovering during the entrance snaps the card to the hover offset, and leaving the hover snaps it back to nothing rather than to the entrance position.

The usual workaround is structural — wrap the card in a div, let the outer element own the entrance and the inner one own the hover — and it works, at the cost of a DOM node per effect and a component that is harder to lay out. The individual transform properties remove the conflict at the source.

Root cause: one property, one value

transform holds an ordered list of functions, and CSS has no mechanism for two rules to each contribute part of a list. The cascade picks a winner for the whole declaration. Two rules that both want to translate the element are not merged; the more specific one wins outright, and the other’s contribution disappears.

This is also why transform function order is such a common source of confusion. translateX(40px) scale(2) moves 40 px and then doubles; scale(2) translateX(40px) doubles and then moves 80 px in the scaled coordinate space. When two authors each write half the list, the result depends on which of them wrote it — which is not a stable foundation.

The longhands make each function its own property with its own cascade resolution and its own interpolation. Composition order is fixed by the specification — translate, rotate, scale, then transform — so two rules can safely own different slots without coordinating. That is the entire benefit, and it is a large one.

Two rules, one elementThe shorthand forces the two effects to negotiate; the longhands let each own its own value.Two rules, one elementBoth write transformCascade picks one winner per elementHover erases the entrance offsetFixed by adding a wrapper elementFunction order changes the geometryStructural workaround requiredtranslate + scaleEach rule owns a different propertyBoth contributions always applyNo extra DOM nodeComposition order fixed by the specNo negotiation needed
The shorthand forces the two effects to negotiate; the longhands let each own its own value.

Decision matrix

Which slot each kind of motion belongs inThe shorthand is not deprecated — it is where composite and 3D functions still live.Which slot each kind of motion belongs intranslate / rotate / scaletransform shorthandComponent stateoffsetCorrectConflicts with other effectsHover or focusemphasisCorrectOnly if nothing else transformsEntrance andexit motionCorrectConflicts with interactionIdle decorativeloopWorksCorrectmatrix() or 3DfunctionsNo longhand existsCorrectperspective()or skew()No longhand existsCorrect
The shorthand is not deprecated — it is where composite and 3D functions still live.

Production code pattern

/* Intent: three independent effects on one card, no wrapper elements,
   each of them separately gateable for reduced motion. */
.card {
  /* Slot 1 — owned by the reveal state. */
  translate: 0 14px;
  /* Slot 2 — owned by pointer feedback. */
  scale: 1;
  /* Slot 3 — owned by the drag handle only. */
  rotate: 0deg;

  opacity: 0;
  transition:
    translate 320ms cubic-bezier(0, 0, 0.2, 1),
    scale     150ms cubic-bezier(0.2, 0, 0, 1),
    rotate    150ms linear,
    opacity   320ms linear;
}

.card.is-revealed { translate: 0 0; opacity: 1; }
.card:hover       { scale: 1.03; }          /* leaves translate alone */
.card.is-dragging { rotate: 1.5deg; }       /* leaves both others alone */

/* Accessibility gate: remove the travel, keep the feedback that tells a
   user their pointer is over the control. */
@media (prefers-reduced-motion: reduce) {
  .card {
    transition: opacity 120ms linear, scale 100ms linear;
    translate: 0 0;      /* no rise */
    rotate: 0deg;        /* no tilt */
  }
  .card:hover { scale: 1.01; }   /* smaller, but still present */
}

Rendering Impact: composite only. Three transitions on three properties still produce one matrix per frame; the compositor cost is identical to a single transform transition, and no wrapper element is laid out.

The reduced-motion block is where the split pays off most visibly. With a single transform list, removing the rise means restating the whole list without it — including the hover scale, which then has to be duplicated. With separate slots, the gate neutralises exactly the property that travels and leaves the rest of the component alone.

Refactoring an element off the shorthandDo it in one commit per element; a half-migrated element is harder to reason about than either end state.Refactoring an element off the shorthand1Grep for every rule that sets transform on the element, includingkeyframes.The count is the number of slots you need2Assign each rule a slot: state to translate, feedback to scale, tilt torotate.No two rules share a property3Rewrite the transition list to name the longhands individually.Each effect gets its own duration and curve4Delete any wrapper element that existed only to isolate a transform.One fewer box in the layout tree5Re-check the reduced-motion branch: it should now touch one property, notrestate a list.
Do it in one commit per element; a half-migrated element is harder to reason about than either end state.

Verification checklist

Constraints and trade-offs

  • There are no longhands for skew(), matrix(), perspective() or the 3D functions; those stay in the shorthand.
  • transform-origin applies to the combined result, so two effects that need different origins still need different boxes.
  • A transform: none declaration does not reset the longhands, and a translate: none does not reset the shorthand — resetting an element fully means naming all four.
  • transition: all now covers four properties instead of one, which makes an accidental transition on an unrelated change more likely; name the properties explicitly.
  • Very old engines ignore the longhands entirely and render the element untransformed, so author the resting position as the base style rather than as the animated value.

Frequently asked questions

In what order are the individual properties applied?

Always translate, then rotate, then scale, and then whatever the transform shorthand declares. The order is fixed by the specification rather than by declaration order, which is precisely what makes the properties safe for two independent authors to use on the same element.

Do the longhands override the transform shorthand?

No, they compose with it. If an element has both, the browser builds the matrix from the longhands first and then appends the shorthand’s function list. What looks like an override is normally the same offset being expressed twice.

Is there a performance difference between the two forms?

No. Both resolve to a single transformation matrix per frame and both animate on the compositor. Two transitions on two longhands cost the same as one transition on the shorthand.


Interaction with keyframe animations

Everything above concerns transitions, where each property resolves independently through the cascade. Keyframe animations add one wrinkle worth knowing before a refactor.

A @keyframes rule can animate the longhands exactly as it animates the shorthand, and the composition order is unchanged — translate, rotate, scale, then transform. What changes is the interaction between two animations on the same element. Two keyframe animations that both write transform contend for one property and resolve by animation order; two that write different longhands do not contend at all, which removes most of the cases where animation composition used to be necessary.

That has a practical consequence for entrance effects. An entrance animation that writes translate and an idle loop that writes rotate can run simultaneously with no coordination, no animation-composition: add, and no risk that a browser without composition support renders only one of them. Composition is still the answer when two effects genuinely need to contribute to the same function — two independent translations, say — but that turns out to be rarer than it looks.

The one caveat is transform-origin, which applies to the combined matrix rather than per property. An entrance that rotates about the top-left and a hover that scales about the centre still need two boxes, because there is only one origin to go around.