CSS Transform
CSS transforms are one of those “tiny syntax, huge power” features. You can rotate, scale, move, skew, and even do 3D flips. And because transforms don’t trigger layout (they don’t shove other elements around), they’re often the smoothest way to animate UI.
In this tutorial we’ll go step-by-step, with interactive playgrounds, from the classic
transform: ... property to the newer individual transform properties:
translate, rotate, and scale.
What CSS transform does (and does not) do
- Transforms change how an element is drawn (its visual coordinate space), not its “real” space in the document flow.
- Because of that, transforms usually don’t affect surrounding layout. Your element can visually overlap neighbors without pushing them.
-
A transformed element can create a new stacking context, which sometimes changes how
z-indexbehaves (useful, but can surprise you).
.demo {
transform: translateX(140px);
}
.demo {
margin-left: 140px;
}
.demo {
position: relative;
left: 140px;
}
*, ::before, ::after {
box-sizing: border-box;
}
.stage {
display: grid;
gap: 14px;
padding: 16px;
border: 3px solid #111;
max-width: 720px;
margin: 0 auto;
font-family: ui-monospace, SFMono-Regular, Menlo;
}
.row {
display: flex;
gap: 14px;
}
.label {
font-weight: 700;
}
.demo {
width: 120px;
height: 80px;
background: #ffd54a;
border: 3px solid #111;
display: grid;
place-items: center;
}
.neighbor {
width: 140px;
height: 80px;
border: 3px dashed #111;
display: grid;
place-items: center;
background: #f5f5f5;
}
Move meI stay putTry the snippets: transform moves the box visually without changing layout. margin-left affects layout (it pushes space). left moves visually, but it depends on positioning.
Two ways to transform: classic vs individual properties
There are now two main approaches:
-
Classic: one property that contains a list of transform functions:
transform: translate(...) rotate(...) scale(...); -
Individual transform properties: separate properties for common transforms:
translate,rotate, andscale.
The individual properties are nice because you can tweak rotation without rewriting your whole transform string.
They are described as being “independent” of transform.
.card {
transform: translateY(-10px) rotate(-6deg) scale(1.05);
}
.card {
translate: 0 -10px;
rotate: -6deg;
scale: 1.05;
}
.card {
translate: 0 -10px;
rotate: -6deg;
scale: 1.05;
transform: skewX(-6deg);
}
*,
::before,
::after {
box-sizing: border-box;
}
.wrap {
display: grid;
place-items: center;
padding: 18px;
border: 3px solid #111;
max-width: 760px;
margin: 0 auto;
margin-top: 40px;
font-family: ui-monospace, SFMono-Regular, Menlo;
}
.card {
width: min(520px, 92%);
border: 3px solid #111;
background: #fff;
padding: 18px;
border-radius: 18px;
box-shadow: 0 14px 0 #111;
}
.card h3 {
margin: 0 0 8px 0;
font-size: 18px;
}
.card p {
margin: 0;
line-height: 1.4;
}
.hint {
margin-top: 12px;
font-size: 13px;
}
Transforms, two styles
Switch snippets to compare classic
transformvs individual properties.Third snippet shows you can mix them: individual transforms apply, then the
transformlist is applied too.
Order matters: the transform list is not commutative
The classic transform property is a list of functions. The key idea:
changing the order changes the result.
Rotate then translate is not the same as translate then rotate.
.box {
transform: translateX(160px) rotate(25deg);
}
.box {
transform: rotate(25deg) translateX(160px);
}
.box {
transform: translateX(160px) rotate(25deg) scale(1.1);
}
.box {
transform: scale(1.1) rotate(25deg) translateX(160px);
}
*, ::before, ::after {
box-sizing: border-box;
}
.stage {
border: 3px solid #111;
max-width: 760px;
margin: 0 auto;
padding: 18px;
font-family: ui-monospace, SFMono-Regular, Menlo;
}
.grid {
display: grid;
grid-template-columns: 1fr;
gap: 10px;
}
.track {
height: 410px;
border: 3px dashed #111;
position: relative;
overflow: hidden;
background: linear-gradient(90deg, #f5f5f5 0 50%, #ffffff 50% 100%);
}
.origin {
position: absolute;
margin: auto;
inset: 0;
width: 12px;
height: 12px;
background: #111;
border-radius: 50%;
}
.box {
width: 130px;
height: 130px;
border: 3px solid #111;
background: #7ee7ff;
display: grid;
place-items: center;
position: absolute;
margin: auto;
inset: 0;
}
.note {
font-size: 13px;
margin: 0;
}
The black dot is the starting point. Watch how the box’s “path” changes when you reorder functions.
box
CSS transform property (the classic workhorse)
The transform property takes one or more transform functions:
translate(), rotate(), scale(), skew(), and a bunch of 3D
variants.
It’s widely supported and the “default” approach you’ll see everywhere.
Classic transform quick syntax tour
.demo {
transform: rotate(12deg);
}
.demo {
transform: scale(1.15);
}
.demo {
transform: translate(30px, -14px);
}
.demo {
transform: skewX(-12deg);
}
.demo {
transform: rotate(10deg) translateX(24px) scale(1.1);
}
*,
::before,
::after {
box-sizing: border-box;
}
.wrap {
display: grid;
place-items: center;
padding: 18px;
border: 3px solid #111;
max-width: 760px;
margin: 0 auto;
font-family: ui-monospace, SFMono-Regular, Menlo;
}
.demo {
width: 220px;
height: 220px;
border: 3px solid #111;
border-radius: 18px;
display: grid;
place-items: center;
background: #ffe9a8;
box-shadow: 0 16px 0 #111;
}
.demo strong {
font-size: 16px;
}
transform me
Individual transform properties: translate, rotate, scale
The newer properties let you do the common transforms without building a long function list.
One very important detail:
the order is fixed for individual properties (it’s not “the order you write them”).
A common mental model is: translate happens first, then rotate, then scale.
If you need a very specific custom order (or you need skew), you’ll still use classic
transform.
.card {
translate: 90px -20px;
rotate: -10deg;
scale: 1.10;
}
*,
::before,
::after {
box-sizing: border-box;
}
.wrap {
max-width: 840px;
margin: 0 auto;
border: 3px solid #111;
padding: 16px;
font-family: ui-monospace, SFMono-Regular, Menlo;
}
.grid {
display: grid;
grid-template-columns: 1fr;
gap: 12px;
}
.stage {
height: 320px;
border: 3px dashed #111;
position: relative;
overflow: hidden;
background: radial-gradient(circle at 20% 30%, #ffffff 0%, #f2f2f2 70%);
}
.card {
width: 240px;
height: 160px;
border: 3px solid #111;
border-radius: 16px;
background: #c7ffcf;
display: grid;
place-items: center;
position: absolute;
left: 60px;
top: 90px;
box-shadow: 0 14px 0 #111;
}
.card strong {
text-align: center;
line-height: 1.2;
}
Individual transforms
(translate / rotate / scale)Use the sliders. Notice how you can tweak rotation without touching translate, and vice-versa.
CSS transform rotate
Rotation can be done in two main ways:
-
Classic:
transform: rotate(25deg); -
Individual property:
rotate: 25deg;
For 2D rotation, you’ll mostly use degrees (deg), but other angle units exist (turn,
rad).
.badge {
transform: rotate(25deg);
}
.badge {
rotate: 25deg;
}
.badge {
transform: rotate(0.1turn);
}
.badge {
transform: rotate(-18deg);
}
*,
::before,
::after {
box-sizing: border-box;
}
.wrap {
max-width: 760px;
margin: 0 auto;
border: 3px solid #111;
padding: 16px;
font-family: ui-monospace, SFMono-Regular, Menlo;
}
.row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
align-items: center;
}
.badge {
width: 190px;
height: 190px;
border-radius: 999px;
border: 3px solid #111;
background: #ffd0f0;
display: grid;
place-items: center;
box-shadow: 0 14px 0 #111;
}
.badge strong {
text-transform: uppercase;
letter-spacing: 0.08em;
}
.note {
margin: 0;
line-height: 1.4;
font-size: 14px;
}
rotateSwitch snippets to compare
transform: rotate()vsrotate:. Both rotate, but the individual property can be easier to tweak without rewriting a full transform string.
Rotate and transform-origin (pivot points)
Rotation happens around a pivot point called the transform origin. By default, it’s the center of the element.
We’ll do a deeper transform-origin section later, but here’s a preview: changing the origin makes rotation look totally different.
.square {
transform: rotate(25deg);
transform-origin: 50% 50%;
}
.square {
transform: rotate(25deg);
transform-origin: 0% 0%;
}
.square {
transform: rotate(25deg);
transform-origin: 100% 100%;
}
.square {
transform: rotate(25deg);
transform-origin: 50% 120%;
}
*, ::before, ::after {
box-sizing: border-box;
}
.wrap {
max-width: 820px;
margin: 0 auto;
border: 3px solid #111;
padding: 16px;
font-family: ui-monospace, SFMono-Regular, Menlo;
}
.stage {
height: 320px;
border: 3px dashed #111;
position: relative;
overflow: hidden;
background: #f7f7f7;
}
.square {
width: 170px;
height: 170px;
border: 3px solid #111;
background: #bfe1ff;
display: grid;
place-items: center;
position: absolute;
margin: auto;
inset: 0;
box-shadow: 0 14px 0 #111;
}
.pivot {
position: absolute;
left: 50%;
top: 50%;
width: 10px;
height: 10px;
background: #111;
border-radius: 50%;
transform: translate(-50%, -50%);
z-index: 1;
}
.note {
margin: 12px 0 0 0;
font-size: 14px;
}
pivotThe black dot marks the stage center. In each snippet, the square rotates around a different origin.
Learn more about rotate in the CSS Rotate Interactive Tutorial.
CSS transform scale
Scaling changes an element’s size in its own coordinate space.
Again, you can do it with classic transform: scale() or the individual scale property.
-
scale(1)means “normal size”. -
scale(1.2)means “20% bigger”. -
Values under 1 shrink (example:
0.8).
.thing {
transform: scale(1.2);
}
.thing {
scale: 1.2;
}
.thing {
transform: scale(0.85);
}
.thing {
transform: scale(1.2, 0.8);
}
*,
::before,
::after {
box-sizing: border-box;
}
.wrap {
max-width: 820px;
margin: 0 auto;
border: 3px solid #111;
padding: 16px;
font-family: ui-monospace, SFMono-Regular, Menlo;
}
.row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
align-items: center;
}
.stage {
height: 320px;
border: 3px dashed #111;
background: #f7f7f7;
display: grid;
place-items: center;
overflow: hidden;
}
.thing {
width: 190px;
height: 190px;
border: 3px solid #111;
background: #fff2b7;
border-radius: 20px;
display: grid;
place-items: center;
box-shadow: 0 14px 0 #111;
}
.note {
margin: 0;
line-height: 1.4;
font-size: 14px;
}
scaleTip: If you’re scaling UI on hover, also consider
transform-originand leaving enough room so it doesn’t overlap important neighbors. Also, scaling up can make text look a bit softer on some screens (normal).
scaleX and scaleY (stretching in one direction)
You can scale only one axis:
-
scaleX(1.3)stretches horizontally. -
scaleY(0.7)squishes vertically.
.panel {
transform: scaleX(1.35);
}
.panel {
transform: scaleY(0.7);
}
.panel {
transform: scaleX(1.25) scaleY(0.8);
}
.panel {
transform: scaleX(-1);
}
*,
::before,
::after {
box-sizing: border-box;
}
.wrap {
max-width: 820px;
margin: 0 auto;
border: 3px solid #111;
padding: 16px;
font-family: ui-monospace, SFMono-Regular, Menlo;
}
.stage {
height: 300px;
border: 3px dashed #111;
display: grid;
place-items: center;
background: #f7f7f7;
overflow: hidden;
}
.panel {
width: 260px;
height: 140px;
border: 3px solid #111;
border-radius: 18px;
background: #d8c8ff;
display: grid;
place-items: center;
box-shadow: 0 14px 0 #111;
}
.note {
margin: 12px 0 0 0;
font-size: 14px;
line-height: 1.4;
}
scaleX / scaleYThe last snippet uses
scaleX(-1)which mirrors horizontally. It’s a fun trick (and a great way to flip an icon).
Learn more about scale in the CSS Scale Interactive Tutorial.
CSS transform translate (X, Y, and Z)
Translate moves an element in 2D or 3D space.
You can use classic translate() functions or the individual translate property.
-
translateX(…)moves horizontally. -
translateY(…)moves vertically. -
translateZ(…)moves “toward/away” in 3D (needs perspective to feel real).
.mover {
translate: 120px -20px;
}
*,
::before,
::after {
box-sizing: border-box;
}
.wrap {
max-width: 860px;
margin: 0 auto;
border: 3px solid #111;
padding: 16px;
font-family: ui-monospace, SFMono-Regular, Menlo;
}
.stage {
height: 320px;
border: 3px dashed #111;
position: relative;
overflow: hidden;
background: linear-gradient(0deg, #f7f7f7 0 60%, #ffffff 60% 100%);
}
.mover {
width: 160px;
height: 160px;
border: 3px solid #111;
border-radius: 18px;
background: #ffe0cc;
display: grid;
place-items: center;
position: absolute;
left: 80px;
top: 80px;
box-shadow: 0 14px 0 #111;
}
.gridlines {
position: absolute;
inset: 0;
background-image:
linear-gradient(#00000012 1px, transparent 1px),
linear-gradient(90deg, #00000012 1px, transparent 1px);
background-size: 40px 40px;
pointer-events: none;
}
.note {
margin: 12px 0 0 0;
font-size: 14px;
line-height: 1.4;
}
translateThis uses the individual
translateproperty (two values: X and Y). It’s very slider-friendly.
translateZ (and why perspective matters)
translateZ() (or the third value in 3D transforms) moves along the Z-axis, but without perspective it
can look like “nothing happened”.
In 3D, you usually combine perspective with a Z translation or a rotateX/rotateY.
.card {
transform: perspective(700px) translateZ(120px) rotateY(-20deg);
}
*,
::before,
::after {
box-sizing: border-box;
}
.wrap {
max-width: 900px;
margin: 0 auto;
border: 3px solid #111;
padding: 16px;
font-family: ui-monospace, SFMono-Regular, Menlo;
}
.stage {
height: 360px;
border: 3px dashed #111;
display: grid;
place-items: center;
background: radial-gradient(circle at 30% 30%, #ffffff 0%, #f2f2f2 70%);
overflow: hidden;
}
.card {
width: 320px;
height: 200px;
border: 3px solid #111;
border-radius: 20px;
background: #b7ffd9;
display: grid;
place-items: center;
box-shadow: 0 18px 0 #111;
transform-style: preserve-3d;
}
.card strong {
text-align: center;
line-height: 1.2;
}
.note {
margin: 12px 0 0 0;
font-size: 14px;
line-height: 1.4;
}
3D space
perspective + translateZWe’re using
perspective(700px)insidetransform. Without perspective, Z motion is much less noticeable.
Learn more about translate in the CSS Translate Interactive Tutorial.
CSS transform skew
Skew “slants” an element. It’s fun for stylized UI, but easy to overdo (your text will look… crunchy).
-
skewX(…)slants horizontally. -
skewY(…)slants vertically. -
skew(… , …)does both.
.ticket {
transform: skewX(-14deg);
}
.ticket {
transform: skewY(10deg);
}
.ticket {
transform: skew(-12deg, 6deg);
}
.ticket {
transform: skewX(-14deg) rotate(-4deg);
}
*,
::before,
::after {
box-sizing: border-box;
}
.wrap {
max-width: 860px;
margin: 0 auto;
border: 3px solid #111;
padding: 16px;
font-family: ui-monospace, SFMono-Regular, Menlo;
}
.stage {
height: 320px;
border: 3px dashed #111;
display: grid;
place-items: center;
background: #f7f7f7;
overflow: hidden;
}
.ticket {
width: 360px;
height: 170px;
border: 3px solid #111;
border-radius: 18px;
background: #ffe3ff;
display: grid;
place-items: center;
box-shadow: 0 16px 0 #111;
}
.ticket p {
margin: 0;
text-align: center;
line-height: 1.2;
}
.small {
font-size: 13px;
opacity: 0.85;
}
Skew
Use responsibly
CSS transform-origin
transform-origin sets the point around which a transformation is applied.
For rotations, it’s literally the center of rotation.
For scaling and skewing, it changes the pivot point too.
The most common values are:
-
Keywords:
left,center,right,top,bottom -
Percentages:
0% 0%,50% 50%,100% 100% -
Lengths:
20px 30px(less common, but sometimes handy)
.arm {
transform: rotate(-18deg);
transform-origin: 50% 50%;
}
.arm {
transform: rotate(-18deg);
transform-origin: 10% 50%;
}
.arm {
transform: rotate(-18deg);
transform-origin: left center;
}
.arm {
transform: rotate(-18deg);
transform-origin: 50% 120%;
}
*, ::before, ::after {
box-sizing: border-box;
}
.wrap {
max-width: 900px;
margin: 0 auto;
border: 3px solid #111;
padding: 16px;
font-family: ui-monospace, SFMono-Regular, Menlo;
}
.stage {
height: 340px;
border: 3px dashed #111;
background: #f7f7f7;
display: grid;
place-items: center;
overflow: hidden;
}
.arm {
width: 360px;
height: 90px;
border: 3px solid #111;
border-radius: 999px;
background: #c7f0ff;
display: grid;
place-items: center;
box-shadow: 0 14px 0 #111;
position: relative;
}
.note {
margin: 12px 0 0 0;
font-size: 14px;
line-height: 1.4;
}
pivot demo
CSS transform matrix
You’ll eventually see things like matrix(...) or matrix3d(...).
This is the “raw math” representation of transforms.
- Good news: you rarely need to write matrices by hand.
- Realistic use case: you copy a matrix from a design tool, or you read it from computed styles, or a JS library generates it.
- Debugging use case: matrices explain why transforms combine the way they do (everything becomes a matrix under the hood).
Matrix is just translate, rotate, scale, and skew in disguise
A 2D matrix has 6 values: matrix(a, b, c, d, e, f).
Roughly:
-
eandfoften correspond to translation. - The other values combine rotation, scale, and skew.
You don’t need to memorize that. What you do need is: if you see a matrix, it’s still a transform.
.box {
transform: rotate(14deg) translateX(60px) scale(1.1);
}
.box {
transform: matrix(1.05, 0.25, -0.18, 1.05, 60, 0);
}
.box {
transform: matrix(1, 0, 0, 1, 60, 0);
}
*,
::before,
::after {
box-sizing: border-box;
}
.wrap {
max-width: 920px;
margin: 0 auto;
border: 3px solid #111;
padding: 16px;
font-family: ui-monospace, SFMono-Regular, Menlo;
}
.stage {
height: 320px;
border: 3px dashed #111;
position: relative;
overflow: hidden;
background: #f7f7f7;
}
.box {
width: 160px;
height: 160px;
border: 3px solid #111;
border-radius: 18px;
background: #ffe5a8;
display: grid;
place-items: center;
position: absolute;
left: 80px;
top: 80px;
box-shadow: 0 14px 0 #111;
}
.note {
margin: 12px 0 0 0;
font-size: 14px;
line-height: 1.4;
}
matrixThe matrix values are intentionally “not fun to read.” That’s the point: prefer normal transform functions unless you truly need matrices.
CSS transform animation and transition
Transforms are popular for animation because they can be very smooth. Two common ways to animate:
- Transitions for state changes (hover, focus, open/close).
- Keyframe animations for continuous motion (loading spinners, floating badges, attention grabbers).
Transform transition (hover card)
.card {
transition: transform 220ms ease;
}
.card:hover {
transform: translateY(-10px) rotate(-2deg) scale(1.03);
}
.card {
transition: translate 220ms ease, rotate 220ms ease, scale 220ms ease;
}
.card:hover {
translate: 0 -10px;
rotate: -2deg;
scale: 1.03;
}
.card {
transition: transform 220ms ease;
}
.card:hover {
transform: translateY(-10px) rotate(-2deg) scale(1.03);
}
.card:focus-visible {
outline: 4px solid #111;
outline-offset: 6px;
}
*,
::before,
::after {
box-sizing: border-box;
}
.wrap {
max-width: 920px;
margin: 0 auto;
border: 3px solid #111;
padding: 16px;
font-family: ui-monospace, SFMono-Regular, Menlo;
}
.grid {
display: grid;
grid-template-columns: 1fr;
gap: 10px;
}
.card {
width: min(720px, 100%);
border: 3px solid #111;
border-radius: 18px;
padding: 18px;
background: #d6f2ff;
box-shadow: 0 16px 0 #111;
display: grid;
gap: 8px;
cursor: pointer;
}
.card h4 {
margin: 0;
font-size: 18px;
}
.card p {
margin: 0;
line-height: 1.4;
font-size: 14px;
}
.card button {
justify-self: start;
border: 3px solid #111;
background: #fff;
padding: 10px 12px;
border-radius: 12px;
font: inherit;
cursor: pointer;
}
Hover (or focus) me
Transforms are perfect for subtle lift effects: translate + rotate + scale.
Snippet 1 uses classic
transform. Snippet 2 uses individual properties and transitions them separately.
Learn more about CSS transitions in the CSS Transition Interactive Tutorial.
Transform keyframes (spinner and wobble)
.spinner {
animation: spin 900ms linear infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
.badge {
animation: floaty 900ms ease-in-out infinite alternate;
}
@keyframes floaty {
to {
transform: translateY(-12px) rotate(-6deg);
}
}
.badge {
animation: floaty 900ms ease-in-out infinite alternate;
}
@keyframes floaty {
to {
translate: 0 -12px;
rotate: -6deg;
}
}
*,
::before,
::after {
box-sizing: border-box;
}
.wrap {
max-width: 920px;
margin: 0 auto;
border: 3px solid #111;
padding: 16px;
font-family: ui-monospace, SFMono-Regular, Menlo;
}
.row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
align-items: center;
}
.stage {
height: 360px;
border: 3px dashed #111;
background: #f7f7f7;
display: grid;
place-items: center;
overflow: hidden;
}
.spinner {
width: 120px;
height: 120px;
border-radius: 999px;
border: 12px solid #111;
border-left-color: transparent;
}
.badge {
width: 240px;
height: 160px;
border-radius: 18px;
border: 3px solid #111;
background: #ffd0d0;
display: grid;
place-items: center;
box-shadow: 0 16px 0 #111;
}
.badge p {
margin: 0;
text-align: center;
line-height: 1.2;
}
.note {
margin: 0;
font-size: 14px;
line-height: 1.4;
}
floaty
keyframesSnippet 1: classic rotate animation. Snippet 2: classic translate + rotate. Snippet 3: same motion using individual
translateandrotate.
Learn more about CSS animations in the CSS Animation Interactive Tutorial.
Common transform bugs and debugging checklist
- “My transform doesn’t do anything.” Confirm you’re targeting the right element and the right selector.
- “It moves but it doesn’t push anything.” That’s normal: transforms don’t affect layout. Use margin/padding/positioning when you need layout changes.
-
“My rotate is weird.”
Check
transform-origin. It’s often the real culprit. -
“My 3D translateZ looks flat.”
Add perspective (either with
perspective()in the transform list, or with a parent perspective setup). -
“My combined transform looks wrong.”
Re-check the order of functions in
transform. Reordering can completely change the result. - “I’m mixing individual properties and transform and I’m confused.” Remember they can combine. Specs define how the final matrix is computed, and practical guides call out the ordering behavior.
Practical tips for real projects
- Prefer transforms for motion (hover lifts, subtle slides, flips) because they usually feel smoother than animating layout properties.
-
Keep transform lists readable:
if your transform value becomes a 3-liner, consider using individual properties for
translate/rotate/scale and reserve
transformfor skew or special ordering. -
Use small angles and small distances for UI motion.
A tiny rotate like
-1degcan add “life” without looking like a carnival ride. -
When you need a progressive enhancement approach,
you can use
@supportsto prefer individual properties and fall back to classic transforms (handy becausetransformis widely supported).
Wrap-up
You now have the full transform toolkit:
-
The classic
transformproperty and transform functions (rotate, scale, translate, skew). -
The newer, more modular
translate,rotate, andscaleproperties. -
How
transform-originchanges the pivot and feel of transforms. - Why matrix exists (and why you usually don’t hand-write it).
- How to animate transforms with transitions and keyframes.
CSS Transform Conclusion
CSS transforms are a powerful way to create dynamic, engaging user interfaces. With the knowledge you’ve gained, you can confidently apply transforms to add motion and interactivity to your projects. Remember to experiment and have fun with it!
