Blog

CSS Custom Properties for Stacked Transforms

September 14, 2022

This post was originally written for the Echobind blog.

tl;dr

  • Use the transform properties, like rotate or translate to apply transformations.
  • Combine CSS custom properties for each transformation to apply stacked transforms from different selectors

Stacked Transforms

The power that CSS Transforms provide is undeniable, but it comes at a cost. Anyone who’s ever tried to combine transformations using multiple selectors will tell you - it’s not as easy as you’d think.

Take a look at this

.box {
  width: 50px;
  height: 50px;
  background-color: rebeccapurple;
}
.rotate {
  transform: rotate(45deg);
}
.translate {
  transform: translate(100px, 0px);
}

Suppose you were to use them with this HTML element: <div class="box translate rotate"></div>. What do you think the result would be?

Since the .translate class is below the .rotate class in the CSS file, the styles override, and the translate property isn’t combined. The end result is a box that is translated 100px to the right. The rotation is completely ignored.

Fortunately, new properties have been added to most browsers which allows each transform to be applied separately.

.rotate {
  rotate: 45deg;
}
.translate {
  translate: 100px 0px;
}

This neatly solves that problem, but it introduces another one.

Applying Multiple Transformations

It’s not obvious, but applying multiple transformations with the transform property actually stacks the transformations. If you apply a rotate before a translate, the element will actually translate based on the rotation that was previously applied. The same thing goes for a scale translation - it multiplies any translate transforms by however much the element was scaled.

The separate rotate, scale, and transform CSS properties apply their transforms atomically, without considering other transforms which might have been applied. In other words, the separate transform properties do not stack.

Sometimes stacking transforms is exactly the behavior you want, but you once again run into the problem of combining transforms from different selectors. To solve that, we need to be a little tricky.

Custom Property Transforms

Using CSS Custom Properties, we can create selectors which let us change individual transform properties without overriding the entire transform. We create a separate CSS Custom Property for each transform property we want to change, and then combine them all in a single .transform class. Here’s what that looks like.

.transform {
  --translate-x: 0;
  --translate-y: 0;
  --rotate: 0;
  --scale-x: 1;
  --scale-y: 1;
  transform: translateX(var(--translate-x)) translateY(var(--translate-y)) rotate(var(--rotate)) scaleX(var(--scale-x)) scaleY(var(--scale-y));
}
.rotate-45 {
  --rotate: 45deg;
}
.translate-x-100 {
  --translate-x: 100px;
}

Then we can apply our classes like this: <div class="box transform rotate-45 translate-x-100">.

If this looks a lot like atomic CSS libraries, like Tailwind, you’d be right! This is exactly the technique Tailwind uses to apply transforms.

This doesn’t give us much flexibility for stacking our transforms, though. It only lets us apply translates, then rotations, then scales. Fortunately, creating separate classes that alter the order is trivial at this point.

.transform-rotate-first {
  transform: rotate(var(--rotate)) translateX(var(--translate-x)) translateY(var(--translate-y)) scaleX(var(--scale-x)) scaleY(var(--scale-y));
}

Adding that class to our element would make it rotate first, then translate the element using whatever angle it was rotated to.