Skip to content
HexSlate

CSS Transform Generator

Front
CSS
.element {
  transform: perspective(800px) translate3d(0px, 0px, 20px) rotateX(8deg) rotateY(-12deg);
}
Tailwind v4
[transform:perspective(800px)_translate3d(0px,_0px,_20px)_rotateX(8deg)_rotateY(-12deg)]
The same thing as one matrix
matrix3d(0.978148, -0.028936, 0.205888, -0.000257, 0, 0.990268, 0.139173, -0.000174, -0.207912, -0.136132, 0.968628, -0.001211, 0, 0, 20, 0.975)
4 functions76 characters of CSS

Transform functions do not commute

transform: rotate(45deg) translateX(100px) and transform: translateX(100px) rotate(45deg) put the element in two different places. The list is applied left to right as a chain of coordinate-system changes, so in the first the element rotates and then moves 100px along its own now-tilted X axis, ending up down and to the right. In the second it moves 100px along the page's X axis and then spins in place. This is the single most common source of "why is my rotation off", and no amount of adjusting the numbers fixes it, because the problem is the order.

Underneath, each function is a 4x4 matrix and the list is their product. Matrix multiplication is associative but not commutative, which is the formal statement of the paragraph above. The matrix box shows what the whole list collapses to; paste that single matrix3d() in place of the function list and you get exactly the same rendering, which is also what the browser hands back if you ask for the computed style.

Perspective is what separates 3D from a skew

A rotateY() with no perspective is an orthographic projection: the far edge stays exactly as long as the near edge, so the element reads as squashed rather than turned. perspective() adds the vanishing point. Smaller values are a stronger effect, because the number is the distance from the viewer to the z=0 plane: 400px is a dramatic wide-angle look, 1000px to 1500px is the subtle one most interfaces want.

There are two ways to write it and they are not the same. The perspective() function inside transform gives each element its own vanishing point directly above its own centre. The perspective property on a parent gives every child one shared vanishing point, which is what you want for a group of cards that should look like they are in one scene. This tool emits the function form, since that is the one that fits in a single declaration.

transform-origin moves the pivot

Everything above happens around a point, and that point defaults to the centre of the border box, 50% 50%. Move it to 0% 100% and a rotation swings the element around its bottom-left corner, which is how a door hinge, a fan-out menu or a card flipping off a stack are built. It applies to scale and skew too: scaling from 0% 0% grows an element down and to the right rather than outward from its middle.

Common problems

  • Text goes blurry after a transform. A non-integer translation, or a scale that lands the element on a half pixel, makes the browser resample the rasterised text. Scale from a larger base size rather than up from a smaller one, or round the translation to whole pixels.
  • The transform does nothing. It does not apply to non-replaced inline elements. A bare <span> needs display: inline-block before it will move.
  • The 3D effect collapses on the children. Each element flattens its descendants by default. Put transform-style: preserve-3d on the parent, and note that it is ignored the moment the same element also has overflow: hidden, a filter, or an opacity below 1.
  • A fixed-position child stopped being fixed. Any transform other than none creates a containing block for descendants, so position: fixed inside it becomes fixed relative to the transformed element instead of the viewport. This also affects filters and will-change: transform.

Frequently asked questions

Why animate transform instead of top and left?

transform and opacity are the two properties a browser can animate on the compositor, without recalculating layout or repainting. Animating top, left, width or margin forces layout on every frame for the element and often for its siblings, which is where dropped frames come from on a mid-range phone.

Does a transform affect layout?

No. The element is painted somewhere else but its box still occupies its original space, so neighbours do not move. That is the dotted outline in the preview above. It does affect hit testing: clicks land where the element is drawn, not where its box is.

What about the individual properties?

translate, rotate and scale also exist as standalone properties, and they are applied in that fixed order before whatever transform says. They are genuinely useful for animating one axis without rewriting the whole value, and they are baseline in current browsers. The single transform declaration stays the portable choice, which is why it is what this tool emits.

Can I paste the matrix back in?

Yes. matrix3d() takes the sixteen values in column-major order and is the exact equivalent of the function list beside it. It is unreadable in a diff, so it is worth having as a check rather than as the thing you commit.