Groups & Transforms

Organizing and transforming SVG with <g> and the transform attribute

Grouping with <g>

The <g> element is a container that groups child elements together. Groups have three main uses: they let you share presentation attributes (inherited by every child), apply a single transform to multiple shapes at once, and logically organize complex drawings so they are easier to reason about.

When you set fill, stroke, or opacity on a <g>, every descendant inherits those values — unless the child overrides them. This is the same CSS inheritance model you already know, applied to SVG presentation attributes.

Shared fill and stroke on a group

<svg viewBox="0 0 260 100" width="320" height="123" role="img" aria-label="Three shapes sharing the same fill and stroke via a parent g element"> <!-- All three shapes share fill and stroke from the group. The middle rect overrides fill to show inheritance can be broken. --> <g fill="#0f766e" stroke="#333" stroke-width="2"> <circle cx="45" cy="50" r="32" /> <rect x="100" y="18" width="64" height="64" rx="6" fill="#d6452c" /> <!-- overrides group fill --> <polygon points="215,18 245,82 185,82" /> </g> </svg>

The circle and triangle inherit fill="#0f766e" from the group. The rectangle overrides it with its own fill="#d6452c". All three shapes share the stroke="#333" stroke-width="2" defined on the group — no need to repeat it on each element.

Transform functions

The transform attribute accepts one or more transform functions. SVG's transform functions mirror the CSS transform functions you may already know from web animation, but they use SVG user units (not CSS pixel values or deg keywords — just bare numbers for SVG transforms).

translate(tx ty)

Moves the shape's coordinate system by tx units right and ty units down. This is the most common transform — useful for repositioning a group without changing the coordinates of each child.

<svg viewBox="0 0 220 80" width="280" height="101" role="img" aria-label="Two identical rectangles; the right one translated 120 units to the right"> <!-- Original position --> <rect x="10" y="20" width="80" height="40" rx="4" fill="#1f8a4c" opacity="0.35" /> <!-- Translated 120 units right --> <rect x="10" y="20" width="80" height="40" rx="4" fill="#1f8a4c" transform="translate(120 0)" /> </svg>

rotate(deg) and rotate(deg cx cy)

Rotates clockwise by the given number of degrees. The three-argument form rotate(deg cx cy) rotates around the point (cx, cy) in the current coordinate system, which is essential for rotating a shape about its own center. The two-argument form rotates around the SVG origin (0, 0), which usually sends the shape flying off-screen — see the rotation-origin section below for why.

<svg viewBox="0 0 220 80" width="280" height="101" role="img" aria-label="Two identical rectangles; the right one rotated 20 degrees about its own center"> <!-- No rotation --> <rect x="20" y="20" width="80" height="40" rx="4" fill="#e07b00" opacity="0.35" /> <!-- Rotated 20deg about the center of the same rect (60,40) --> <rect x="20" y="20" width="80" height="40" rx="4" fill="#e07b00" transform="rotate(20 60 40)" /> </svg>

scale(sx sy)

Scales the coordinate system. With one argument scale(s) the same factor applies to both axes; with two arguments scale(sx sy) you can stretch or compress independently. Like rotation without a center, scale by default expands outward from the origin — a common surprise for beginners.

<svg viewBox="0 0 220 100" width="280" height="127" role="img" aria-label="A circle scaled to 1.5x its original size using transform scale, centered in its group via translate"> <!-- Original circle --> <circle cx="55" cy="50" r="30" fill="#0f766e" opacity="0.3" /> <!-- Scale 1.5x about the circle's center: translate to center, scale, translate back --> <circle cx="55" cy="50" r="30" fill="#0f766e" transform="translate(55 50) scale(1.5) translate(-55 -50)" /> </svg>

skewX(deg) and skewY(deg)

Shears the coordinate system along the X or Y axis. skewX(deg) tilts vertical lines by deg degrees — turning a rectangle into a parallelogram. This is the SVG equivalent of the italic slant used in type design.

<svg viewBox="0 0 260 100" width="320" height="123" role="img" aria-label="Three rectangles: original, skewed on X axis, and skewed on Y axis"> <rect x="10" y="20" width="60" height="50" rx="3" fill="#d6452c" opacity="0.35" /> <text x="40" y="88" text-anchor="middle" font-family="sans-serif" font-size="11" fill="#555">original</text> <rect x="10" y="20" width="60" height="50" rx="3" fill="#d6452c" transform="translate(90 0) skewX(20)" /> <text x="150" y="88" text-anchor="middle" font-family="sans-serif" font-size="11" fill="#555">skewX(20)</text> <rect x="10" y="20" width="60" height="50" rx="3" fill="#1f8a4c" transform="translate(180 0) skewY(15)" /> <text x="250" y="88" text-anchor="middle" font-family="sans-serif" font-size="11" fill="#555">skewY(15)</text> </svg> original skewX(20) skewY(15)

Combining transforms — order matters

When you write multiple transform functions in a single transform attribute, they are applied right to left — the last one listed executes first. This is the same convention as matrix multiplication. The result: "translate then rotate" places the shape differently than "rotate then translate".

A good mental model is that each transform function establishes a new local coordinate system. When you write transform="translate(60 0) rotate(40)", the rotate happens first in the original coordinate space, then the translate moves the already-rotated coordinate system 60 units to the right. Reverse them and you rotate a coordinate system that has already been shifted.

Side-by-side: same functions, opposite order

translate(60,0) rotate(40)
rotate(40) translate(60,0)

Both SVGs have the same ghost rectangle (gray) at the same origin and apply the same two functions — only the order differs. In the first case, the rectangle translates 60 units along the original horizontal axis, then rotates in place. In the second, the rectangle first rotates 40 degrees around the origin, which tilts its local X axis — so the subsequent translate moves it along the tilted axis, placing it in a completely different spot.

This is why transform order matters: each function modifies the coordinate system for all the functions to its left.

Rotation origin

rotate(deg) with a single argument rotates around the SVG origin — the top-left corner of the viewport. Most of the time this is not what you want. To spin a shape in place, you need to rotate around the shape's own center.

SVG provides two ways to do this:

  • rotate(deg cx cy) — the three-argument form rotates around the specific point (cx, cy) in the current coordinate system. This is the simplest approach when you know the center coordinates.
  • Translate–rotate–translate back — the general pattern: translate(cx cy) rotate(deg) translate(-cx -cy). Equivalent to the three-argument form but works with other transform functions too (since you cannot specify a center for scale).

Rotate about shape center vs. SVG origin

rotate(35) — about SVG origin
rotate(35 80 70) — about own center

The gray ghost rectangle shows the original position in both cases. rotate(35) (left) swings the shape in a wide arc around (0, 0) — the top-left corner of the SVG. rotate(35 80 70) (right) spins it about (80, 70), which is the center of that rectangle (x + width/2 = 50 + 30 = 80; y + height/2 = 50 + 20 = 70), leaving it roughly in place but tilted.

If you are animating with CSS (transform-origin on the SVG element's CSS), be aware that CSS transforms on SVG elements use the CSS coordinate space, not the SVG user coordinate space. The SVG attribute transform always works in SVG user units, which is why the three-argument rotate form is more predictable for SVG-only work.