CSStransform-origin

transform-origin

Every transform — rotate(), scale(), skew() — happens around a fixed reference point on the element, and transform-origin is what sets that point. Changing it is the difference between a button that grows evenly from its center and one that grows outward from a specific corner, or a card that spins in place versus one that swings like a door on a hinge.

The Default: Dead Center

Without setting transform-origin yourself, every element transforms around its own center — equivalent to transform-origin: 50% 50% (also writable as center center, or just center).

CSS
.box {
  transform-origin: 50% 50%; /* the default — center of the element */
  transform: rotate(45deg);
}
Syntax

transform-origin accepts one or two values (an x-offset and a y-offset), each as a percentage, a length, or a keyword (top, bottom, left, right, center). A third value can set a z-offset for 3D transforms.

CSS
.top-left     { transform-origin: 0 0; }          /* top-left corner */
.top-left-2   { transform-origin: top left; }      /* same, as keywords */
.bottom-right { transform-origin: 100% 100%; }     /* bottom-right corner */
.custom-point { transform-origin: 30px 10px; }     /* an exact pixel point */
.right-edge   { transform-origin: right center; }  /* middle of the right edge */
Same rotate(), Wildly Different Results

This is the clearest way to see why transform-origin matters: applying the exact same rotate(45deg) to three boxes, differing only in their origin point, produces three completely different visual results.

CSS
.rotate-center {
  transform-origin: center; /* spins in place, corners sweep outward evenly */
  transform: rotate(45deg);
}

.rotate-top-left {
  transform-origin: top left; /* pivots around its own top-left corner —
                                  the box swings down and to the right */
  transform: rotate(45deg);
}

.rotate-bottom-right {
  transform-origin: bottom right; /* pivots around its bottom-right corner —
                                      the box swings up and to the left */
  transform: rotate(45deg);
}

With a center origin, the box rotates symmetrically in place. With a top left origin, that same corner stays perfectly still while the rest of the box swings around it like a clock hand — the box's overall position on the page visibly shifts even though nothing changed but the origin point.

Practical Use Case: A Flip Card Hinge

CSS
.flip-card {
  perspective: 800px;
}

.flip-card__inner {
  transition: transform 0.5s;
  transform-style: preserve-3d;
  transform-origin: left center; /* hinge along the left edge, like a page */
}

.flip-card:hover .flip-card__inner {
  transform: rotateY(-140deg); /* swings open around the hinge, not the center */
}

Setting the origin to left center makes the rotation read as a door or page swinging open along its left edge, instead of the more generic "spinning card" look a center origin would produce.

Practical Use Case: Zoom-From-Cursor on Hover

CSS
.zoomable {
  transition: transform 0.3s ease;
  transform-origin: var(--zoom-x, 50%) var(--zoom-y, 50%);
}

.zoomable:hover {
  transform: scale(1.4);
}

HTML
<div
  class="zoomable"
  onmousemove="
    const r = this.getBoundingClientRect();
    this.style.setProperty('--zoom-x', ((event.clientX - r.left) / r.width) * 100 + '%');
    this.style.setProperty('--zoom-y', ((event.clientY - r.top) / r.height) * 100 + '%');
  "
>
  <img src="photo.jpg" alt="Product photo" />
</div>

A tiny bit of JavaScript sets the origin's x/y percentage to track the cursor position via custom properties, so scale(1.4) zooms in centered on wherever the user's mouse is — the same technique used by most e-commerce "hover to zoom" product images.

transform-origin needs a transform to have any visible effect
Setting `transform-origin` on its own does nothing visually — it only matters once a `transform` is applied to the same element. Also note percentage values are relative to the element's own bounding box, not the viewport or its parent.
  • Corners (top left, bottom right, etc.) are the most common non-default choice, useful for menus or tooltips that should grow away from their trigger point.

  • An edge midpoint (left center, top center) is the classic hinge/flip setup.

  • Dynamic, cursor-tracked origins (set via a custom property from JavaScript) power hover-to-zoom interactions.

Next
See the full set of individual transform functions this pairs with: [Individual Transform Properties](/css/individual-transforms).