We’re going to learn how to move things around using CSS translate: smooth, GPU-friendly-ish movement that doesn’t cause layout to reshuffle.

There are two main ways you’ll do this in CSS:

  • The classic way: transform: translate(...)
  • The modern, composable way: translate: ... (the translate property)

We’ll cover both, plus translateX(), translateY(), translateZ(), and full 3D with translate3d() and perspective.

What CSS translate does

Translate shifts an element visually along the X and/or Y axis (and Z for 3D):

  • Translate moves the element without changing layout for surrounding elements.
  • So you can animate it smoothly without pushing other content around.

Think of it like sliding a sticker on top of a page. The page layout stays the same; only the sticker’s visual position changes.

CSS translate property

The translate property is part of the “individual transform properties” family: translate, rotate, scale. It lets you apply translation without writing the full transform shorthand.

The typical syntax is:

  • translate: <tx>;
  • translate: <tx> <ty>;
  • translate: <tx> <ty> <tz>; (3D)

Let’s make it interactive and slide a card around.

.mover {
  translate: 0px 0px;
}
  
*,
::before,
::after {
  box-sizing: border-box;
}

.stage {
  width: min(720px, 100%);
  margin: 0 auto;
  padding: 18px;
  border: 3px solid #111;
  background: #f6f6f6;
  border-radius: 14px;
}

.grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 12px;
}

.note {
  font-family: ui-monospace, SFMono-Regular, Menlo;
  font-size: 14px;
  line-height: 1.4;
  background: #fff;
  border: 2px dashed #111;
  border-radius: 12px;
  padding: 12px;
}

.mover {
  width: 220px;
  height: 140px;
  border-radius: 16px;
  border: 3px solid #111;
  background: #ffffff;
  display: grid;
  place-items: center;
  font-family: ui-monospace, SFMono-Regular, Menlo;
  font-size: 16px;
  box-shadow: 0 10px 0 #111;
  position: relative;
}

.mover::before {
  content: "";
  position: absolute;
  inset: 10px;
  border-radius: 12px;
  border: 2px solid #111;
  opacity: 0.15;
}

.crosshair {
  height: 240px;
  display: grid;
  place-items: center;
  border-radius: 14px;
  background:
    linear-gradient(#0000 49%, #1112 50%, #0000 51%),
    linear-gradient(90deg, #0000 49%, #1112 50%, #0000 51%);
}
  
Drag the sliders. The element moves visually, but the surrounding layout stays put.
translate: x y

Translate units and percentages

You can use px, rem, %, and more. Two important beginner notes:

  • Percentages are relative to the element’s own size (not the parent). So translate: 100% 0; typically moves the element one of its own widths to the right.
  • translate doesn’t change document flow. If you translate a thing “over” another thing, they can overlap.

Let’s see that percent behavior quickly.

.mover {
  translate: 0 0;
}
  
.mover {
  translate: 100% 0;
}
  
.mover {
  translate: 100% 50%;
}
  
*,
::before,
::after {
  box-sizing: border-box;
}

.stage {
  width: min(720px, 100%);
  margin: 0 auto;
  padding: 18px;
  border: 3px solid #111;
  background: #f6f6f6;
  border-radius: 14px;
}

.row {
  display: grid;
  grid-template-columns: 1fr;
  gap: 12px;
}

.track {
  height: 220px;
  border: 3px solid #111;
  border-radius: 14px;
  background: #fff;
  display: grid;
  place-items: center;
  position: relative;
  overflow: hidden;
}

.track::before {
  content: "Track (element can overlap)";
  position: absolute;
  top: 10px;
  left: 12px;
  font-family: ui-monospace, SFMono-Regular, Menlo;
  font-size: 13px;
  opacity: 0.6;
}

.mover {
  width: 200px;
  height: 120px;
  border-radius: 16px;
  border: 3px solid #111;
  background: #ffffff;
  display: grid;
  place-items: center;
  font-family: ui-monospace, SFMono-Regular, Menlo;
  box-shadow: 0 10px 0 #111;
}
  
200px wide box

CSS translate vs transform translate

Now the big question: Should you use translate: ... or transform: translate(...)?

They often produce the same visual result, but they differ in how you compose transforms.

  • transform is a single property that can include many functions: translate, rotate, scale, etc.
  • translate is only translation, and it composes with rotate and scale as separate properties.

This can be really helpful when multiple rules or states want to modify different parts of the transform without clobbering each other.

The classic: transform: translate()

.box {
  transform: translate(0px, 0px);
}
  
.box {
  transform: translate(120px, 0px);
}
  
.box {
  transform: translate(120px, 60px);
}
  
*,
::before,
::after {
  box-sizing: border-box;
}

.stage {
  width: min(720px, 100%);
  margin: 0 auto;
  padding: 18px;
  border: 3px solid #111;
  background: #f6f6f6;
  border-radius: 14px;
}

.track {
  height: 220px;
  border: 3px solid #111;
  border-radius: 14px;
  background: #fff;
  display: grid;
  place-items: center;
  position: relative;
}

.box {
  width: 180px;
  height: 120px;
  border-radius: 16px;
  border: 3px solid #111;
  background: #ffffff;
  display: grid;
  place-items: center;
  font-family: ui-monospace, SFMono-Regular, Menlo;
  box-shadow: 0 10px 0 #111;
  transition: transform 300ms ease;
}
  
transform: translate()

The modern: translate: x y

Same idea, but as its own property:

.box {
  translate: 0px 0px;
}
  
.box {
  translate: 120px 0px;
}
  
.box {
  translate: 120px 60px;
}
  
*,
::before,
::after {
  box-sizing: border-box;
}

.stage {
  width: min(720px, 100%);
  margin: 0 auto;
  padding: 18px;
  border: 3px solid #111;
  background: #f6f6f6;
  border-radius: 14px;
}

.track {
  height: 220px;
  border: 3px solid #111;
  border-radius: 14px;
  background: #fff;
  display: grid;
  place-items: center;
  position: relative;
}

.box {
  width: 180px;
  height: 120px;
  border-radius: 16px;
  border: 3px solid #111;
  background: #ffffff;
  display: grid;
  place-items: center;
  font-family: ui-monospace, SFMono-Regular, Menlo;
  box-shadow: 0 10px 0 #111;
  transition: translate 300ms ease;
}
  
translate: x y

So which one should you pick?

  • If you already use transform everywhere and you’re comfortable with it, it’s totally fine.
  • If you want cleaner composition (especially when different states tweak rotate/scale/translate independently), translate is really nice.

CSS translateX

translateX() is a transform function. It’s used inside transform like this:

  • transform: translateX(20px);

It moves the element horizontally. Here’s a quick comparison of a few values.

.puck {
  transform: translateX(0px);
}
  
.puck {
  transform: translateX(120px);
}
  
.puck {
  transform: translateX(-120px);
}
  
*,
::before,
::after {
  box-sizing: border-box;
}

.stage {
  width: min(720px, 100%);
  margin: 0 auto;
  padding: 18px;
  border: 3px solid #111;
  background: #f6f6f6;
  border-radius: 14px;
}

.lane {
  height: 140px;
  border: 3px solid #111;
  border-radius: 999px;
  background: #fff;
  display: grid;
  place-items: center;
  position: relative;
  overflow: hidden;
}

.puck {
  width: 110px;
  height: 110px;
  border-radius: 999px;
  border: 3px solid #111;
  background: #ffffff;
  display: grid;
  place-items: center;
  font-family: ui-monospace, SFMono-Regular, Menlo;
  box-shadow: 0 10px 0 #111;
  transition: transform 300ms ease;
}
  
X

CSS translateY

translateY() moves the element vertically (down for positive values, up for negative values):

  • transform: translateY(20px);
.puck {
  transform: translateY(0px);
}
  
.puck {
  transform: translateY(60px);
}
  
.puck {
  transform: translateY(-60px);
}
  
*,
::before,
::after {
  box-sizing: border-box;
}

.stage {
  width: min(720px, 100%);
  margin: 0 auto;
  padding: 18px;
  border: 3px solid #111;
  background: #f6f6f6;
  border-radius: 14px;
}

.lane {
  height: 240px;
  border: 3px solid #111;
  border-radius: 14px;
  background: #fff;
  display: grid;
  place-items: center;
  position: relative;
  overflow: hidden;
}

.puck {
  width: 140px;
  height: 90px;
  border-radius: 16px;
  border: 3px solid #111;
  background: #ffffff;
  display: grid;
  place-items: center;
  font-family: ui-monospace, SFMono-Regular, Menlo;
  box-shadow: 0 10px 0 #111;
  transition: transform 300ms ease;
}
  
Y

CSS translateZ

translateZ() moves the element toward or away from the viewer in 3D space:

  • transform: translateZ(80px);

Here’s the gotcha: you usually won’t see much change unless perspective is involved. Perspective gives the browser a “camera” view so Z-depth looks like scaling with depth.

We’ll add perspective on the parent, and preserve 3D so children stay in 3D space.

.scene {
  perspective: 700px;
}
.card {
  transform: translateZ(0px);
  transform-style: preserve-3d;
  transition: transform 250ms ease;
}
  
*,
::before,
::after {
  box-sizing: border-box;
}

.stage {
  width: min(720px, 100%);
  margin: 0 auto;
  padding: 18px;
  border: 3px solid #111;
  background: #f6f6f6;
  border-radius: 14px;
}

.scene {
  height: 260px;
  border: 3px solid #111;
  border-radius: 14px;
  background: #fff;
  display: grid;
  place-items: center;
  position: relative;
  overflow: hidden;
}

.scene::before {
  content: "perspective: 700px";
  position: absolute;
  top: 10px;
  left: 12px;
  font-family: ui-monospace, SFMono-Regular, Menlo;
  font-size: 13px;
  opacity: 0.6;
}

.card {
  width: 220px;
  height: 140px;
  border-radius: 16px;
  border: 3px solid #111;
  background: #ffffff;
  display: grid;
  place-items: center;
  font-family: ui-monospace, SFMono-Regular, Menlo;
  box-shadow: 0 12px 0 #111;
  
}

.card::after {
  content: "Z";
  transform: translateZ(1px);
  opacity: 0.5;
}
  
translateZ()

CSS translate X and Y

Most real-world translate usage is “move it a bit this way and that way.” Here are some common patterns:

  • Nudging icons: translate: 2px 1px; (tiny pixel shifts for alignment)
  • Hover lift: translate: 0 -6px;
  • Centering trick: move by -50% after positioning at 50%

Centering with translate(-50%, -50%)

This is a classic: position an element at left: 50% and top: 50%, then translate it back by half of its own size.

.modal {
  left: 50%;
  top: 50%;
  translate: -50% -50%;
}
  
.modal {
  left: 50%;
  top: 50%;
  translate: -50% -50%;
  rotate: -3deg;
}
  
.modal {
  left: 50%;
  top: 50%;
  translate: -50% -50%;
  rotate: 3deg;
}
  
*,
::before,
::after {
  box-sizing: border-box;
}

.stage {
  width: min(720px, 100%);
  margin: 0 auto;
  padding: 18px;
  border: 3px solid #111;
  background: #f6f6f6;
  border-radius: 14px;
}

.backdrop {
  height: 280px;
  border: 3px solid #111;
  border-radius: 14px;
  background: #fff;
  position: relative;
  overflow: hidden;
}

.backdrop::before {
  content: "";
  position: absolute;
  inset: 0;
  background:
    radial-gradient(circle at 20% 20%, #1111 0 30%, #0000 31%),
    radial-gradient(circle at 80% 70%, #1111 0 28%, #0000 29%);
}

.modal {
  position: absolute;
  width: 280px;
  padding: 14px;
  border-radius: 16px;
  border: 3px solid #111;
  background: #ffffff;
  box-shadow: 0 12px 0 #111;
  font-family: ui-monospace, SFMono-Regular, Menlo;
}

.modal p {
  margin: 0;
  font-size: 14px;
  line-height: 1.4;
}
  

CSS translate3d (and perspective)

translate3d(x, y, z) is the 3D version of translate. You’ll typically see it in transform:

  • transform: translate3d(20px, 10px, 80px);

Again: without perspective, Z movement can feel invisible. So we’ll add perspective on the parent and let you push X, Y, and Z.

.tile {
  transform: translate3d(0px, 0px, 0px);
  transform-style: preserve-3d;
  transition: transform 250ms ease;
}
.world {
  perspective: 700px;
}
.floor {
  transform: rotateX(55deg);
  transform-style: preserve-3d;
}
  
*,
::before,
::after {
  box-sizing: border-box;
}

.stage {
  width: min(720px, 100%);
  margin: 0 auto;
  padding: 18px;
  border: 3px solid #111;
  background: #f6f6f6;
  border-radius: 14px;
}

.world {
  height: 280px;
  border: 3px solid #111;
  border-radius: 14px;
  background: #fff;
  display: grid;
  place-items: center;
  perspective: 700px;
  position: relative;
  overflow: hidden;
}

.world::before {
  content: "perspective: 700px";
  position: absolute;
  top: 10px;
  left: 12px;
  font-family: ui-monospace, SFMono-Regular, Menlo;
  font-size: 13px;
  opacity: 0.6;
}

.floor {
  width: 520px;
  height: 220px;
  border-radius: 16px;
  border: 2px dashed #111;
  display: grid;
  place-items: center;
  transform: rotateX(55deg);
  transform-style: preserve-3d;
}

.tile {
  width: 180px;
  height: 120px;
  border-radius: 16px;
  border: 3px solid #111;
  background: #ffffff;
  display: grid;
  place-items: center;
  font-family: ui-monospace, SFMono-Regular, Menlo;
  box-shadow: 0 12px 0 #111;
  transform-style: preserve-3d;
  transition: transform 250ms ease;
}
  
translate3d()

CSS translate and rotate

When you mix translation and rotation, order matters if you’re using the transform shorthand.

  • transform: translate(...) rotate(...) translates first, then rotates the translated element.
  • transform: rotate(...) translate(...) rotates first, then translates along the rotated axes.

This is one of the most common “why is it moving diagonally?!” moments.

.badge {
  transform: translate(120px, 0px) rotate(25deg);
}
  
.badge {
  transform: rotate(25deg) translate(120px, 0px);
}
  
.badge {
  transform: translate(120px, 0px) rotate(-25deg);
}
  
*,
::before,
::after {
  box-sizing: border-box;
}

.stage {
  width: min(720px, 100%);
  margin: 0 auto;
  padding: 18px;
  border: 3px solid #111;
  background: #f6f6f6;
  border-radius: 14px;
}

.frame {
  height: 260px;
  border: 3px solid #111;
  border-radius: 14px;
  background: #fff;
  display: grid;
  place-items: center;
  position: relative;
  overflow: hidden;
}

.origin {
  width: 10px;
  height: 10px;
  background: #111;
  border-radius: 999px;
  position: absolute;
  left: 50%;
  top: 50%;
  translate: -50% -50%;
  opacity: 0.35;
}

.badge {
  width: 170px;
  height: 110px;
  border-radius: 16px;
  border: 3px solid #111;
  background: #ffffff;
  display: grid;
  place-items: center;
  font-family: ui-monospace, SFMono-Regular, Menlo;
  box-shadow: 0 12px 0 #111;
  transition: transform 250ms ease;
}
  
order matters

Composing with translate and rotate properties

If you use translate and rotate as separate properties, the browser composes them for you. This can be nicer when you want “this state moves it” and “that state rotates it” without rewriting one giant transform string.

.badge {
  translate: 120px 0px;
  rotate: 25deg;
}
  
.badge {
  translate: 120px 0px;
  rotate: -25deg;
}
  
.badge {
  translate: -120px 0px;
  rotate: 25deg;
}
  
*,
::before,
::after {
  box-sizing: border-box;
}

.stage {
  width: min(720px, 100%);
  margin: 0 auto;
  padding: 18px;
  border: 3px solid #111;
  background: #f6f6f6;
  border-radius: 14px;
}

.frame {
  height: 260px;
  border: 3px solid #111;
  border-radius: 14px;
  background: #fff;
  display: grid;
  place-items: center;
  position: relative;
  overflow: hidden;
}

.origin {
  width: 10px;
  height: 10px;
  background: #111;
  border-radius: 999px;
  position: absolute;
  left: 50%;
  top: 50%;
  translate: -50% -50%;
  opacity: 0.35;
}

.badge {
  width: 170px;
  height: 110px;
  border-radius: 16px;
  border: 3px solid #111;
  background: #ffffff;
  display: grid;
  place-items: center;
  font-family: ui-monospace, SFMono-Regular, Menlo;
  box-shadow: 0 12px 0 #111;
  transition: translate 250ms ease, rotate 250ms ease;
}
  
translate + rotate

Translate animation and performance notes

Beginner-friendly rule of thumb:

  • Animating translate (or transform) is usually smoother than animating layout properties like top, left, margin.
  • That’s because transforms typically avoid layout recalculation and can be handled more efficiently by the browser.

Also: translated elements can overlap and create stacking contexts depending on other properties. If something disappears behind something else, check z-index and whether a new stacking context was created.

Learn more in the CSS Z-Index Interactive Tutorial.

CSS translate not working and debugging

If translate “does nothing,” it’s usually one of these:

  • You’re translating the wrong element. Add a border temporarily and confirm which box is moving.
  • You’re being overwritten. Another rule sets transform (or translate) later in the cascade.
  • You used the wrong property. translateX() is a transform function, not a standalone property.
  • You used percentages expecting the parent. Remember: translate: 50% 0; is based on the element’s own size.
  • 3D without perspective. If translateZ() seems invisible, add perspective on a parent.
  • Order confusion in transform. rotate() translate() moves along rotated axes, which feels “wrong” until it clicks.

Quick debug checklist

  1. Inspect the element in DevTools and check the computed value of transform and/or translate.
  2. Temporarily set outline: 3px solid #111; to confirm you’re targeting the right box.
  3. Search your CSS for other transform: declarations on the same element (common with hover states).
  4. If using translateZ or translate3d, add perspective to a parent and try again.
  5. If something overlaps incorrectly, test position and z-index (and watch for stacking contexts).

Wrap up

You now have a full translate toolkit:

  • translate: x y; for clean, composable movement
  • transform: translate(), translateX(), translateY() for classic transform workflows
  • translateZ() and translate3d() for 3D movement (with perspective)
  • And the biggest brain upgrade: transform order matters

CSS translate conclusion

CSS translate is a powerful tool for moving elements around without affecting layout. Whether you use translate as its own property or translate() inside transform, you can create smooth animations, hover effects, and dynamic layouts.

Learn more about CSS transforms in general in the CSS Transform Interactive Tutorial.