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>| Command | Meaning | Parameters |
|---|---|---|
M / m | Move to | x y — starts a new subpath |
L / l | Line to | x y |
H / h | Horizontal line | x |
V / v | Vertical line | y |
C / c | Cubic Bezier | x1 y1 x2 y2 x y — two control points |
S / s | Smooth cubic | x2 y2 x y — first control point reflected |
Q / q | Quadratic Bezier | x1 y1 x y — one control point |
T / t | Smooth quadratic | x y — control point reflected |
A / a | Elliptical arc | rx ry rotation large-arc sweep x y |
Z / z | Close the subpath | None |
💡
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-flagchooses the arc spanning more than 180 degrees; thesweep-flagchooses 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.
SandTare 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. */| Technique | What it does | Whose job |
|---|---|---|
pathLength | Normalises length to a chosen value | Author or build step |
getTotalLength() | Returns the real length in user units | Runtime script |
getPointAtLength(d) | A point at a distance along the path | Runtime script |
stroke-dasharray + stroke-dashoffset | Draws or hides part of a stroke | CSS or SMIL |
fill-rule: evenodd | Holes from overlapping subpaths | Presentation attribute or CSS |
clip-rule | The same for a clipPath | Same |
| Reversing subpaths | Changes which areas evenodd sees as holes | Authoring |
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.Related
Shapes and coordinates Transforms, clipping and masking
Last refreshed 2026-09-18.