Paths in depth: commands, curves and arcs

Read and write path data fluently, choose between quadratic, cubic and arc curves, and use the tricks that only path data can do.

The command vocabulary

<svg viewBox="0 0 200 120" xmlns="http://www.w3.org/2000/svg">
  <!-- Absolute commands use capitals and absolute coordinates -->
  <path d="M20 100 L60 40 L100 100 Z" fill="none" stroke="#334155" stroke-width="2"/>

  <!-- The same shape with relative commands: lowercase, offsets from the last point -->
  <path d="m20 100 l40 -60 l40 60 z" fill="none" stroke="#6d28d9" stroke-width="2"/>

  <!-- H and V: horizontal and vertical, skipping one coordinate -->
  <path d="M120 100 H180 V40 H120 Z" fill="rgba(14,165,233,0.15)" stroke="#0ea5e9" stroke-width="2"/>

  <!-- S: a smooth cubic. The first control point is reflected automatically. -->
  <path d="M10 20 C40 0 60 40 90 20 S140 0 170 20"
        fill="none" stroke="#10b981" stroke-width="2"/>
</svg>
CommandMeaningParameters
M / mMove tox y — starts a new subpath
L / lLine tox y
H / hHorizontal linex
V / vVertical liney
C / cCubic Bezierx1 y1 x2 y2 x y — two control points
S / sSmooth cubicx2 y2 x y — first control point reflected
Q / qQuadratic Bezierx1 y1 x y — one control point
T / tSmooth quadraticx y — control point reflected
A / aElliptical arcrx ry rotation large-arc sweep x y
Z / zClose the subpathNone
💡
An uppercase command starts a new coordinate system for the numbers that follow: every parameter is absolute. A lowercase command makes every following pair an offset from the previous point. Mixing them in one d attribute is legal and extremely confusing — pick one style per path.

Choosing a curve

<svg viewBox="0 0 260 120" xmlns="http://www.w3.org/2000/svg">
  <!-- Quadratic: one control point, a single smooth bend. Cheapest. -->
  <path d="M10 100 Q 60 10 110 100" fill="none" stroke="#6d28d9" stroke-width="2"/>
  <circle cx="60" cy="10" r="2.5" fill="#6d28d9"/>

  <!-- Cubic: two control points, an S-shape or any asymmetric bend. -->
  <path d="M130 100 C 150 10, 210 10, 250 100" fill="none" stroke="#0ea5e9" stroke-width="2"/>
  <circle cx="150" cy="10" r="2.5" fill="#0ea5e9"/>
  <circle cx="210" cy="10" r="2.5" fill="#0ea5e9"/>

  <!-- Arc: part of an ellipse, defined by radii, a rotation and two flags. -->
  <!-- rx ry rotation large-arc-flag sweep-flag x y -->
  <path d="M10 20 A 30 30 0 0 1 70 20" fill="none" stroke="#10b981" stroke-width="2"/>
  <path d="M80 20 A 30 30 0 0 0 140 20" fill="none" stroke="#10b981" stroke-width="2" opacity="0.5"/>
  <path d="M150 20 A 30 20 45 1 1 210 20" fill="none" stroke="#f59e0b" stroke-width="2"/>
</svg>
  • The large-arc-flag chooses the arc spanning more than 180 degrees; the sweep-flag chooses the direction. Both are 0 or 1, and the four combinations produce four different arcs between the same two points.
  • If the radii are too small to reach between the endpoints, the browser scales them up uniformly until they fit. That is why a malformed arc still draws something.
  • A quadratic curve is enough when the shape is symmetric. A cubic is required for an S-curve, because the two control points need to be on opposite sides.
  • S and T are the reason long path data can stay short: a series of joined curves only needs one control point per segment.
<!-- A circle drawn with two arcs, and the same shape as four cubics.
     Arcs are shorter and exactly circular; cubics are only approximately so. -->
<path d="M100 50 A 50 50 0 1 0 100 150 A 50 50 0 1 0 100 50" />

<!-- Rounded rectangle with arcs: rx and ry are the corner radii -->
<path d="M20 0 H80 A10 10 0 0 1 90 10 V50 A10 10 0 0 1 80 60 H20 A10 10 0 0 1 10 50 V10 A10 10 0 0 1 20 0 Z" />

<!-- Why you would hand-write that instead of using <rect rx>: it composes with
     other path commands in one element, so a single animation or clipPath
     applies to the whole shape. -->

pathLength and drawing tricks

<!-- pathLength normalises the measured length of the path, so
     stroke-dasharray and stroke-dashoffset can be expressed as fractions
     of the total. This is what makes a "draw the line" animation easy. -->
<svg viewBox="0 0 200 60" xmlns="http://www.w3.org/2000/svg">
  <path d="M10 50 C 60 0, 140 60, 190 10"
        fill="none" stroke="#6d28d9" stroke-width="3"
        pathLength="1"
        stroke-dasharray="1"
        stroke-dashoffset="1">
    <animate attributeName="stroke-dashoffset" from="1" to="0" dur="1.2s" fill="freeze"/>
  </path>
</svg>
/* The CSS version of the same idea, without knowing the real length. */
.draw {
  pathLength: 1;               /* not a CSS property: set it as an attribute */
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
  animation: draw 1.2s ease-out forwards;
}
@keyframes draw { to { stroke-dashoffset: 0; } }

/* Without pathLength you must measure in JavaScript:
     const length = path.getTotalLength();
     path.style.strokeDasharray = length;
     path.style.strokeDashoffset = length;
   Its advantages: it works on any path without editing the markup, and
   getPointAtLength() gives you points along the way. */
TechniqueWhat it doesWhose job
pathLengthNormalises length to a chosen valueAuthor or build step
getTotalLength()Returns the real length in user unitsRuntime script
getPointAtLength(d)A point at a distance along the pathRuntime script
stroke-dasharray + stroke-dashoffsetDraws or hides part of a strokeCSS or SMIL
fill-rule: evenoddHoles from overlapping subpathsPresentation attribute or CSS
clip-ruleThe same for a clipPathSame
Reversing subpathsChanges which areas evenodd sees as holesAuthoring

One more habit worth adopting: keep the d attribute readable by putting one command per line with its parameters, and use a formatting tool to round the numbers. Path data is the one part of SVG a human still edits by hand, and a diff of a 3,000-character one-line d is unreadable.

FAQ

How do I know which arc flags to use?
Picture the two endpoints and ask whether you want the arc that bulges more than half a circle (large-arc-flag = 1) or less (0), and whether the sweep should go clockwise (1) or anticlockwise (0) in the SVG coordinate system where y grows downward. Draw both and look — the four combinations are genuinely easier to try than to reason about.
Why does my path look chunky when scaled?
It is not a path problem; the path is resolution independent. The likely causes are a bitmap filter applied to it, a very small viewBox with a large stroke, or an export from a tool that converted the shape to a raster image inside the SVG.

Shapes and coordinates Transforms, clipping and masking

Last refreshed 2026-09-18.