Skip to main content

Harshal V. LADHE

SVG Essentials: Mastering the Path Element

Understand SVG paths, curves, and commands
Published at:
Last updated:
Estimated reading time:19 min read

If basic shapes like circles and rectangles are the "Lego bricks" of SVG, the <path> element is the professional pen tool. It is a single, ultra-powerful element capable of drawing any shape imaginable — from custom icons to fluid, organic illustrations — by following a string of specific commands.

Before we dive into the "alphabet" of path data, it is important to have a solid grasp of how the SVG canvas works. If you aren't yet comfortable with the top-left coordinate system or the logic of the viewBox, I recommend starting there first.

Once you have the grid down, you're ready to master the d attribute and move from simple geometry to advanced vector mastery.

The d Attribute: The Map of Your Shape

The <path> element relies almost entirely on one attribute: d (which stands for data). This attribute contains a series of commands and coordinates that tell the browser exactly where to move the "virtual pen."

Example of simple triangle:

<path d="M10 80 L100 20 L190 80 Z" fill="lightblue" stroke="blue" stroke-width="2"/>

Absolute vs. Relative Coordinates

This is one of the most important "secrets" of SVG paths. The casing of the letter command changes how the browser interprets the numbers:

  • UPPERCASE (e.g., L 50 50): Absolute coordinates. "Draw a line to exactly the coordinate (50, 50) on the grid."
  • lowercase (e.g., l 50 50): Relative coordinates. "Draw a line 50 units right and 50 units down from where the pen currently is."

Why professionals prefer relative commands:

  • Easier manual editing
  • Cleaner exports
  • Better compression

Moving and Drawing Lines

Straight-line commands form the structural skeleton of every SVG path. Even the most complex icons are built on these primitives before curves are added.

MoveTo (M / m)

The MoveTo command positions the pen without drawing. Every path must begin with a move command.

If multiple coordinate pairs are provided, the first pair moves the pen and the rest are treated as implicit LineTo commands.

Syntax:

  • Absolute: M x y - move to absolute coordinates (x,y)
  • Relative: m dx dy - move relative to current position

Mental model:

Lift the pen, move to a location, don't draw.

Example:

Path Command: M (MoveTo)

0
20
100
0
20
100
<svg viewBox="0 0 100 100" width="240" height="240">
  <!-- Coordinate guides -->
  <line x1="20" y1="0" x2="20" y2="100" stroke="var(--color-border-default)" stroke-width="0.5" stroke-dasharray="2 2" opacity="0.45" />
  <line x1="0" y1="20" x2="100" y2="20" stroke="var(--color-border-default)" stroke-width="0.5" stroke-dasharray="2 2" opacity="0.45" />

  <!-- MoveTo point -->
  <circle cx="20" cy="20" r="2" fill="#6366f1" opacity="0.2" />
  <circle cx="20" cy="20" r="1" fill="#6366f1" />

  <!-- M command -->
  <path d="M 20 20" fill="none" stroke="#6366f1" stroke-width="1" />
</svg>

LineTo (L / l)

Draws a straight line from the current position to the specified point.

Syntax:

  • Absolute: L x y
  • Relative: l dx dy

Implicit Behavior:

M10 10 L20 20 30 30 40 40 draws three lines without repeating L — once a command letter is set, extra coordinate pairs keep reusing it.

Example:

Path Command: L (LineTo)

0
10
100
0
10
100
0
90
100
0
90
100
<svg viewBox="0 0 100 100" width="240" height="240">
  <!-- Coordinate guides -->
  <line x1="10" y1="10" x2="90" y2="10" stroke="var(--color-border-default)" stroke-width="0.5" stroke-dasharray="2 2" opacity="0.5" />
  <line x1="90" y1="10" x2="90" y2="90" stroke="var(--color-border-default)" stroke-width="0.5" stroke-dasharray="2 2" opacity="0.5" />

  <!-- Main path -->
  <path d="M 10 10 L 90 90" fill="none" stroke="#6366f1" stroke-width="1.5" stroke-linecap="round" />

  <!-- Start point -->
  <circle cx="10" cy="10" r="2" fill="#6366f1" opacity="0.2" />
  <circle cx="10" cy="10" r="1" fill="#6366f1" />

  <!-- End point -->
  <circle cx="90" cy="90" r="2" fill="#6366f1" opacity="0.2" />
  <circle cx="90" cy="90" r="1" fill="#6366f1" />
</svg>

Horizontal LineTo (H / h)

Draws a horizontal line while keeping the current y value unchanged.

Syntax:

  • Absolute: H x
  • Relative: h dx

When to use:

  • Pixel-perfect edges
  • UI shapes
  • Charts and grids

Example:

Path Command: H (Horizontal LineTo)

0
90
100
<svg viewBox="0 0 100 100" width="240" height="240">
  <!-- Horizontal construction guide -->
  <line x1="10" y1="50" x2="90" y2="50" stroke="var(--color-border-default)" stroke-width="0.5" stroke-dasharray="2 2" opacity="0.5" />

  <!-- Vertical guide to endpoint -->
  <line x1="90" y1="20" x2="90" y2="80" stroke="var(--color-border-default)" stroke-width="0.5" stroke-dasharray="2 2" opacity="0.4" />

  <path d="M 10 50 H 90" fill="none" stroke="#10b981" stroke-width="1.5" stroke-linecap="round" />

  <!-- Start -->
  <circle cx="10" cy="50" r="1" fill="var(--color-fg-muted)" />

  <!-- End -->
  <circle cx="90" cy="50" r="2" fill="#10b981" opacity="0.2" />
  <circle cx="90" cy="50" r="1" fill="#10b981" />
</svg>

Vertical LineTo (V / v)

Draws a vertical line while keeping the current x value unchanged.

Syntax:

  • Absolute: V y
  • Relative: v dy

Example:

Path Command: V (Vertical LineTo)

0
90
100
<svg viewBox="0 0 100 100" width="240" height="240">
  <!-- Vertical construction guide -->
  <line x1="50" y1="10" x2="50" y2="90" stroke="var(--color-border-default)" stroke-width="0.5" stroke-dasharray="2 2" opacity="0.5" />

  <!-- Horizontal guide -->
  <line x1="20" y1="90" x2="80" y2="90" stroke="var(--color-border-default)" stroke-width="0.5" stroke-dasharray="2 2" opacity="0.4" />

  <path d="M 50 10 V 90" fill="none" stroke="#f43f5e" stroke-width="1.5" stroke-linecap="round" />

  <!-- Start -->
  <circle cx="50" cy="10" r="1" fill="var(--color-fg-muted)" />

  <!-- End -->
  <circle cx="50" cy="90" r="2" fill="#f43f5e" opacity="0.2" />
  <circle cx="50" cy="90" r="1" fill="#f43f5e" />
</svg>

ClosePath (Z / z)

Closes the current sub-path by drawing a straight line back to the starting point of the last M.

  • No coordinates required
  • Resets the current point
  • Essential for filled shapes

z and Z perform the exact same action, unlike other commands.

Mental model:

Example:

Path Command: Z (ClosePath)

0
50
100
0
20
100
0
80
100
0
80
100
<svg viewBox="0 0 100 100" width="240" height="240">
  <!-- Closing edge guide -->
  <line x1="80" y1="80" x2="20" y2="80" stroke="var(--color-border-default)" stroke-width="0.75" stroke-dasharray="2 2" opacity="0.6" />

  <!-- One L, two coordinate pairs — the second "L" is implicit, per the post's shorthand tricks. -->
  <path d="M 20 80 L 50 20 80 80 Z" fill="#6366f1" fill-opacity="0.2" stroke="#6366f1" stroke-width="1.5" stroke-linejoin="round" />

  <!-- Start / close point -->
  <circle cx="20" cy="80" r="2" fill="#6366f1" opacity="0.2" />
  <circle cx="20" cy="80" r="1" fill="#6366f1" />

  <!-- Peak -->
  <circle cx="50" cy="20" r="2" fill="#6366f1" opacity="0.2" />
  <circle cx="50" cy="20" r="1" fill="#6366f1" />

  <!-- End -->
  <circle cx="80" cy="80" r="1" fill="var(--color-fg-muted)" />
</svg>

Mastering the Curves

Straight lines define structure, but curves define personality. SVG paths support bézier curves and arcs, which allow smooth, organic, and professional-looking shapes.

SVG uses two types of bézier curves and elliptical arcs.

Quadratic Bézier (Q / q)

A Quadratic Bézier curve is defined using one control point and one end point. The curve bends toward the control point but never passes through it.

This type of curve is simpler and lighter than cubic béziers, making it useful for gentle curves and wave-like shapes.

Syntax:

  • Absolute: Q x1 y1, x y (Control point, End point)
  • Relative: q dx1 dy1 dx dy

Mental model:

  • Start point → curve bends toward control point → ends at end point

Example:

Path Command: Q (Quadratic Bézier)

0
10
100
0
50
100
0
50
100
0
10
100
0
90
100
0
50
100
<svg viewBox="0 0 100 100" width="240" height="240">
  <!-- Bézier control skeleton -->
  <path d="M 10 50 L 50 10 90 50" fill="none" stroke="var(--color-border-default)" stroke-width="0.75" stroke-dasharray="2 2" opacity="0.7" />

  <!-- Main curve -->
  <path d="M 10 50 Q 50 10 90 50" fill="none" stroke="#ec4899" stroke-width="1.5" stroke-linecap="round" />

  <!-- Start -->
  <circle cx="10" cy="50" r="1" fill="var(--color-fg-muted)" />

  <!-- Control point -->
  <circle cx="50" cy="10" r="2" fill="#ec4899" opacity="0.2" />
  <circle cx="50" cy="10" r="1" fill="#ec4899" />

  <!-- End -->
  <circle cx="90" cy="50" r="1" fill="var(--color-fg-muted)" />
</svg>

Smooth Quadratic Bézier (T / t)

The T command is a shortcut that continues a quadratic curve smoothly.

It automatically reflects the previous control point, so you only specify the new end point.

Syntax:

  • Absolute: T x y
  • Relative: t dx dy

Why it matters:

  • Perfect for waves, flowing strokes, and handwriting-style paths
  • Eliminates repeated control-point math

Example:

Path Command: T (Smooth Quadratic Bézier)

0
10
50
0
50
100
0
30
100
0
10
100
20
50
80
20
50
80
50
90
100
0
50
100
<svg viewBox="0 0 100 100" width="240" height="240">
  <!-- First quadratic skeleton -->
  <path d="M 10 50 L 30 10 50 50" fill="none" stroke="var(--color-border-default)" stroke-width="0.75" stroke-dasharray="2 2" opacity="0.7" />

  <!-- Reflected control skeleton -->
  <path d="M 50 50 L 70 90 90 50" fill="none" stroke="var(--color-fg-accent)" stroke-width="0.75" stroke-dasharray="2 2" opacity="0.6" />

  <!-- Main path -->
  <path d="M 10 50 Q 30 10 50 50 T 90 50" fill="none" stroke="#ec4899" stroke-width="1.5" stroke-linecap="round" />

  <!-- Start -->
  <circle cx="10" cy="50" r="1" fill="var(--color-fg-muted)" />

  <!-- Original control -->
  <circle cx="30" cy="10" r="2" fill="#ec4899" opacity="0.2" />
  <circle cx="30" cy="10" r="1" fill="#ec4899" />

  <!-- Junction -->
  <circle cx="50" cy="50" r="2" fill="var(--color-fg-muted)" opacity="0.2" />
  <circle cx="50" cy="50" r="1" fill="var(--color-fg-muted)" />

  <!-- Reflected/implicit T control -->
  <circle cx="70" cy="90" r="2" fill="none" stroke="#ec4899" stroke-width="0.75" stroke-dasharray="2 2" />

  <!-- End -->
  <circle cx="90" cy="50" r="1" fill="var(--color-fg-muted)" />
</svg>

Cubic Bézier (C / c)

Cubic Bézier curves are the most powerful and commonly used curve type. They use two control points, allowing complex S curves and precise shaping.

This is the curve type used by design tools like Figma, Illustrator, and Sketch.

Syntax:

  • Absolute: C x1 y1 x2 y2 x y (First control, Second control, End point)
  • Relative: c dx1 dy1 dx2 dy2 dx dy

Mental model:

  • First control point controls the curve's entry direction
  • Second control point controls the curve's exit direction

Example:

Path Command: C (Cubic Bézier)

0
10
100
0
50
100
0
30
50
0
10
100
50
70
100
0
90
100
0
90
100
0
50
100
<svg viewBox="0 0 100 100" width="240" height="240">
  <!-- First control handle -->
  <line x1="10" y1="50" x2="30" y2="10" stroke="var(--color-border-default)" stroke-width="0.75" stroke-dasharray="2 2" opacity="0.7" />

  <!-- Second control handle -->
  <line x1="90" y1="50" x2="70" y2="90" stroke="var(--color-border-default)" stroke-width="0.75" stroke-dasharray="2 2" opacity="0.7" />

  <!-- Main curve -->
  <path d="M 10 50 C 30 10 70 90 90 50" fill="none" stroke="#6366f1" stroke-width="1.5" stroke-linecap="round" />

  <!-- Start -->
  <circle cx="10" cy="50" r="1" fill="var(--color-fg-muted)" />

  <!-- C1 -->
  <circle cx="30" cy="10" r="2" fill="#6366f1" opacity="0.2" />
  <circle cx="30" cy="10" r="1" fill="#6366f1" />

  <!-- C2 -->
  <circle cx="70" cy="90" r="2" fill="#6366f1" opacity="0.2" />
  <circle cx="70" cy="90" r="1" fill="#6366f1" />

  <!-- End -->
  <circle cx="90" cy="50" r="1" fill="var(--color-fg-muted)" />
</svg>

Smooth Cubic Bézier (S / s)

The S command creates a smooth continuation of a cubic bézier curve.

It automatically reflects the second control point of the previous C or S command, so only one control point is required.

Syntax:

  • Absolute: S x2 y2 x y
  • Relative: s dx2 dy2 dx dy

Why it matters:

  • Reduces path complexity
  • Ideal for flowing icons and symmetrical curves

Example:

Path Command: S (Smooth Cubic Bézier)

0
10
100
0
50
100
10
40
50
0
10
100
30
50
70
30
50
70
50
80
100
0
90
100
70
90
100
0
50
100
<svg viewBox="0 0 100 100" width="240" height="240">
  <!-- Previous cubic control skeleton -->
  <path d="M 10 50 L 20 10 40 10 50 50" fill="none" stroke="var(--color-border-default)" stroke-width="0.75" stroke-dasharray="2 2" opacity="0.7" />

  <!-- Smooth reflected control -->
  <path d="M 50 50 L 60 90 80 90 90 50" fill="none" stroke="var(--color-fg-accent)" stroke-width="0.75" stroke-dasharray="2 2" opacity="0.65" />

  <!-- Main path -->
  <path d="M 10 50 C 20 10 40 10 50 50 S 80 90 90 50" fill="none" stroke="#6366f1" stroke-width="1.5" stroke-linecap="round" />

  <!-- Start -->
  <circle cx="10" cy="50" r="1" fill="var(--color-fg-muted)" />

  <!-- Previous C2 -->
  <circle cx="40" cy="10" r="2" fill="#6366f1" opacity="0.2" />
  <circle cx="40" cy="10" r="1" fill="#6366f1" />

  <!-- Junction -->
  <circle cx="50" cy="50" r="2" fill="var(--color-fg-muted)" opacity="0.2" />
  <circle cx="50" cy="50" r="1" fill="var(--color-fg-muted)" />

  <!-- Implicit reflected control -->
  <circle cx="60" cy="90" r="2" fill="none" stroke="#6366f1" stroke-width="0.75" stroke-dasharray="2 2" />

  <!-- S second control -->
  <circle cx="80" cy="90" r="2" fill="#6366f1" opacity="0.2" />
  <circle cx="80" cy="90" r="1" fill="#6366f1" />

  <!-- End -->
  <circle cx="90" cy="50" r="1" fill="var(--color-fg-muted)" />
</svg>

Elliptical Arc (A / a)

The Arc command draws a portion of an ellipse between two points. It is commonly used for circles, rounded corners, and curved UI shapes.

Syntax:

  • Absolute: A rx ry x-axis-rotation large-arc-flag sweep-flag x y
  • Relative: a rx ry x-axis-rotation large-arc-flag sweep-flag dx dy
ParameterMeaning
rx ryX and Y radius of the ellipse
x-axis-rotationRotation of ellipse (degrees)
large-arc-flag0 = small arc, 1 = large arc
sweep-flag0 = counter-clockwise, 1 = clockwise
x yEnd point of the arc

Example:

Path Command: A (Elliptical Arc)

0
20
100
0
50
100
10
30
50
10
20
50
0
0
360
0
80
100
0
50
100
<svg viewBox="0 0 100 100" width="240" height="240">
  <!-- Approximate ellipse used to visualize rx / ry -->
  <ellipse cx="50" cy="50" rx="30" ry="20" fill="none" stroke="var(--color-border-default)" stroke-width="0.75" stroke-dasharray="2 2" opacity="0.55" transform="rotate(0 50 50)" />

  <!-- Start-to-end chord -->
  <line x1="20" y1="50" x2="80" y2="50" stroke="var(--color-border-default)" stroke-width="0.5" stroke-dasharray="2 2" opacity="0.45" />

  <!-- Arc: flags written back-to-back (e.g. "01" instead of "0 1") — see the Path Element post's shorthand tricks. -->
  <path d="M 20 50 A 30 20 0 01 80 50" fill="none" stroke="#10b981" stroke-width="1.5" stroke-linecap="round" />

  <!-- Start point -->
  <circle cx="20" cy="50" r="2" fill="#10b981" opacity="0.2" />
  <circle cx="20" cy="50" r="1" fill="#10b981" />

  <!-- End point -->
  <circle cx="80" cy="50" r="2" fill="#10b981" opacity="0.2" />
  <circle cx="80" cy="50" r="1" fill="#10b981" />

  <!-- Arc midpoint / ellipse center -->
  <circle cx="50" cy="50" r="1" fill="var(--color-fg-muted)" />
</svg>

Why arcs feel complex:

  • Flags (0 / 1) control which arc is drawn
  • SVG chooses between four possible arcs

Seeing All Four Arcs

Any two points on a circle's edge can be connected in four different ways — short or long way around, curving clockwise or counter-clockwise. The two flags each pick one of those two choices:

large-arc-flagsweep-flagResult
00Short way, counter-clockwise
01Short way, clockwise
10Long way, counter-clockwise
11Long way, clockwise

Most "why did my arc go the wrong way" bugs come down to sweep-flag alone, since it's the one that decides direction rather than distance — if the curve bulges the wrong way, flip that flag first before touching anything else.

Path Commands Overview

Here is a complete list of path commands:

CommandTypeCoordinatesDescription
M / mMoveTox yMoves cursor without drawing.
L / lLineTox yDraws a line to given point
H / hHorizontal LineToxDraws horizontal line
V / vVertical LineToyDraws vertical line
Z / zClosePath-Closes path by connecting to start
Q / qQuadratic Bézierx1 y1 x yCurve with 1 control point
T / tSmooth Quadratic Bézierx yQuadratic with control point inferred
C / cCubic Bézierx1 y1 x2 y2 x yCurve with 2 control points
S / sSmooth Cubic Bézierx2 y2 x yCubic with first control point inferred
A / aElliptical Arcrx ry x-axis-rotation large-arc-flag sweep-flag x yDraws elliptical arc

Geometry Rules at a Glance

Just like the basic shapes' geometry rules, <path> has its own set of constraints — scattered across the sections above, but worth compiling into one glance-able reference:

ConceptKey geometryRule to remember
d attributestarts with M / mEvery path must begin with a move — there's no implicit starting point the way <rect> has x/y
Z / zcloses to the last MDetermines open vs. closed, the same role as polyline vs. polygon: unclosed still fills fine, but strokes with a visible gap at the seam
A / arx, ryA zero radius collapses the arc into a straight line; the flags — not the radii — pick which of the 4 possible arcs is drawn
Compound sub-pathsmultiple Ms in one dfill-rule decides which regions count as "inside," not the order or position of the sub-paths
pathLengthoverrides the path's own length unitsThe one attribute that redefines a path's geometry in the abstract — every other shape's geometry is exactly what it says

Tips for Using Paths

  1. Start with MoveTo (M).
  2. Use Z to close shapes.
  3. Prefer H and V for straight edges.
  4. Plan control points for curves visually.
  5. Use relative commands for repeated patterns.
  6. Combine arcs, lines, and curves for icons or illustrations.
  7. Keep strokes visible during debugging (fill="none").

Combining Commands

Paths allow multiple commands in one d attribute:

<path d="M20 80 Q100 10, 180 80 T340 80" stroke="purple" fill="none"/>
  • Mix of move, line, curve, arc
  • Creates complex shapes in a single element

When you look at a complex path exported from a design tool, it can look like gibberish at first. Break it down step-by-step, the same way as any other:

  1. M20 80: Move to (20, 80) — the wave's starting point.
  2. Q100 10, 180 80: Curve up toward (100, 10) and land at (180, 80) — the first hump.
  3. T340 80: Smoothly continue the curve to (340, 80) — T infers the control point by reflecting Q's, so the second hump mirrors the first without a control point ever being typed twice.

This is the actual shape of most hand-authored wave and blob paths: one Q to establish the curve, then a chain of Ts to repeat it.

Compound Paths and the fill-rule Property

A single d attribute isn't limited to one closed shape — starting a new M mid-string begins a new sub-path without ending the element. This is how a donut, a letter "O", or an icon with a cut-out hole gets built: two closed sub-paths in one <path>, the second sitting inside the first.

The catch is that the browser then has to decide which regions count as "inside" the shape, and that decision is controlled entirely by fill-rule. Both squares below share the exact same d — only fill-rule differs:

<svg viewBox="0 0 100 100" width="180" height="180">
  <path d="M10 10 H90 V90 H10 Z M30 30 H70 V70 H30 Z" fill="#6366f1" fill-rule="nonzero" />
</svg>
<svg viewBox="0 0 100 100" width="180" height="180">
  <path d="M10 10 H90 V90 H10 Z M30 30 H70 V70 H30 Z" fill="#6366f1" fill-rule="evenodd" />
</svg>
  • nonzero (the default): both sub-paths above are drawn in the same winding direction (clockwise), so the inner square's winding count never returns to zero — it gets filled in too, producing a solid square with no hole.
  • evenodd: counts how many sub-path boundaries a ray from that point crosses on its way out. The inner square's interior crosses two boundaries (even), so it's treated as outside the fill — punching a hole regardless of which direction either sub-path winds.

Practical Example: Heart Shape

Practical Example: The Heart Shape

15
20
25
70
95
100
40
65
80
<svg viewBox="0 0 100 100" width="240" height="240">
  <!-- Construction guides -->
  <path d="M 90 35 L 90 65 50 95 10 65 10 35" fill="none" stroke="var(--color-fg-accent)" stroke-width="0.75" stroke-dasharray="2 2" opacity="0.45" />

  <!-- Horizontal center guide -->
  <line x1="10" y1="35" x2="90" y2="35" stroke="var(--color-border-default)" stroke-width="0.5" stroke-dasharray="2 2" opacity="0.4" />

  <!--
    Heart: both arcs share the same rx/ry/rotation/flags, so the second "A" is implicit — one A,
    14 numbers (7 per arc) — plus the flags themselves jammed together ("01" instead of "0 1").
    Both are the shorthand covered in the post's Common Pitfalls section.
  -->
  <path d="M 10 35 A 20 20 0 01 50 35 20 20 0 01 90 35 Q 90 65 50 95 Q 10 65 10 35 Z" fill="#f43f5e" fill-opacity="0.2" stroke="#e11d48" stroke-width="1.5" stroke-linejoin="round" />

  <!-- Left hump center -->
  <circle cx="30" cy="35" r="1" fill="var(--color-fg-muted)" />

  <!-- Right hump center -->
  <circle cx="70" cy="35" r="1" fill="var(--color-fg-muted)" />

  <!-- Left bulge control -->
  <circle cx="90" cy="65" r="2" fill="#e11d48" opacity="0.2" />
  <circle cx="90" cy="65" r="1" fill="#e11d48" />

  <!-- Right bulge control -->
  <circle cx="10" cy="65" r="2" fill="#e11d48" opacity="0.2" />
  <circle cx="10" cy="65" r="1" fill="#e11d48" />

  <!-- Tip -->
  <circle cx="50" cy="95" r="2" fill="#e11d48" opacity="0.2" />
  <circle cx="50" cy="95" r="1" fill="#e11d48" />

  <!-- Start point -->
  <circle cx="10" cy="35" r="1" fill="var(--color-fg-muted)" />

  <!-- End / close point -->
  <circle cx="10" cy="35" r="1.5" fill="var(--color-fg-muted)" />
</svg>
  • Combines arcs (A) + quadratic béziers (Q)
  • Closed path forms a heart shape

At its default settings, the demo above is drawing this d string:

M 10 35 A 20 20 0 01 50 35 20 20 0 01 90 35 Q 90 65 50 95 Q 10 65 10 35 Z

Two shorthand tricks from the pitfalls section are already at work here: the arc flags are jammed together (01 instead of 0 1), and there's only one A even though the heart clearly has two arcs.

Decoded piece by piece, the same way we broke down the wave earlier:

  1. M 10 35: Start at the top of the left hump.
  2. A 20 20 0 01 50 35: The first arc, over to (50, 35) — the dip between the two humps. The jammed flags read as large-arc-flag: 0, sweep-flag: 1; that 1 curves it downward into the dip rather than up and over.
  3. 20 20 0 01 90 35: No letter here — this is the second arc, implicit. It reuses the previous command (A) with the exact same 7-value shape (rx ry rotation flags), just a new end point: the top of the right hump. Both humps are identical arcs, which is exactly the case implicit repetition is for.
  4. Q 90 65 50 95: Curve from the right hump down to the bottom point, bulging outward through the control point at (90, 65).
  5. Q 10 65 10 35: Mirror that curve back up to the starting point, closing the left side.
  6. Z: Close the path — technically redundant here since the last point already equals the first, but it's what tells the renderer this is a fillable closed shape rather than an open stroke.

Two arcs for the humps (one spelled out, one implicit), two quadratic curves for the sides — that's the entire heart.

Animating Paths: pathLength and the Line-Draw Setup

The classic "line drawing itself" effect — an icon's stroke appearing to trace itself in on scroll or on load — is built entirely from two stroke properties, stroke-dasharray and stroke-dashoffset, plus one attribute that makes the math trivial: pathLength.

By default, stroke-dasharray's unit is whatever unit the path's own geometry uses, which means the "total length" you need for the dash array depends on the exact shape — a different number for every path. Setting pathLength="100" on the <path> overrides that: the browser rescales its internal length calculations so the path is always exactly 100 units long, no matter its actual size or complexity.

<svg viewBox="0 0 100 100" width="200" height="200">
  <style>
    .draw-me {
      stroke-dasharray: 100;
      stroke-dashoffset: 100;
      animation: draw 2.5s ease forwards infinite;
    }

    @keyframes draw {
      to {
        stroke-dashoffset: 0;
      }
    }
  </style>
  <path
    class="draw-me"
    pathLength="100"
    d="M20 55 L40 75 80 25"
    fill="none"
    stroke="#10b981"
    stroke-width="6"
    stroke-linecap="round"
  />
</svg>

With pathLength="100", the recipe is always the same regardless of the path's own coordinates:

  • stroke-dasharray: 100 — one dash exactly as long as the whole path, with a gap after it also 100 units long.
  • stroke-dashoffset: 100 — shifts that dash fully off the start, so nothing is visible yet.
  • Animating stroke-dashoffset down to 0 slides the dash back into place, revealing the stroke as it goes.

Paths Beyond <svg>: the CSS path() Function

Path syntax isn't limited to the <path> element anymore. The CSS path() function accepts the exact same d-attribute syntax and can drive two properties directly on any HTML or SVG element, with no <svg> wrapper required:

.custom-clip {
  /* Same command letters as the d attribute — clips this element to a pentagon shape. */
  clip-path: path("M110 15 L210 90 L172 210 L48 210 L10 90 Z");
}

.moves-along-a-curve {
  /* Slides the element along the curve instead of clipping to it. */
  offset-path: path("M10 80 Q95 10 180 80");
  offset-distance: 0%;
  animation: travel 3s linear infinite;
}

@keyframes travel {
  to {
    offset-distance: 100%;
  }
}
  • clip-path: path(...) — clips an element to an arbitrary shape without needing an actual <svg> in the markup at all.
  • offset-path: path(...) — moves an element along that path as offset-distance animates from 0% to 100%, with offset-rotate optionally turning the element to face the direction of travel.

Everything learned about commands, curves, and arcs in this post transfers directly — a path() string is read exactly like a d attribute.

Stroke Rendering Details

Two properties decide how a stroked path actually looks at its ends and corners — easy to miss since they only become visible once stroke-width is thick enough to notice. Both apply to any stroked shape, not just <path> — the basic shapes post has a hands-on playground for both if you want to see them toggled live on a simple line and corner first:

  • stroke-linecap (butt / round / square) — how an open path's two loose ends are capped. butt (the default) stops exactly at the endpoint; round and square both extend past it by half the stroke width, round with a curve and square with a flat edge.
  • stroke-linejoin (miter / round / bevel) — how two segments meet at a corner. miter (the default) comes to a sharp point that can spike dramatically on very acute angles; round and bevel both cap that spike, rounded or flat-cut respectively.
<svg viewBox="0 0 260 60" width="390" height="90">
  <path d="M20 45 L20 15" fill="none" stroke="#6366f1" stroke-width="12" stroke-linecap="butt" />
  <path d="M100 45 L100 15" fill="none" stroke="#6366f1" stroke-width="12" stroke-linecap="round" />
  <path d="M180 45 L180 15" fill="none" stroke="#6366f1" stroke-width="12" stroke-linecap="square" />
</svg>

Both properties are CSS as well as SVG presentation attributes, so they're just as reachable from a stylesheet as from the markup itself. One more attribute worth knowing for icons that get resized: vector-effect="non-scaling-stroke" keeps stroke-width at a constant pixel thickness even when the element itself is scaled up or down — without it, a scaled-up icon's stroke scales up right along with the shape, which is rarely what's wanted for UI iconography.

Common Pitfalls and Shorthand Tricks

A handful of small rules explain almost every "why doesn't my path render right" moment — and, in the other direction, why exported path data so rarely has a space in it:

  • Forgetting Z: an unclosed path can still be filled (the renderer implicitly closes it for fill purposes), but its stroke will show a visible gap at the seam. If the outline looks broken but the fill looks fine, this is almost always why.
  • Mixing absolute and relative mid-path: perfectly legal — M10 10 l20 0 L50 50 moves relative for one command and absolute for the next — but it's the easiest way to lose track of where the pen actually is. Stick to one style per path unless there's a specific reason to mix.
  • Arc flags, not radius, are usually the bug: as covered above, a wrong-looking arc is almost always a sweep-flag problem, not an rx/ry problem.

And the reverse side of the same coin — the shorthand rules that let tools (and hand-written paths) drop almost every space and letter:

  • Implicit command repetition: repeat coordinate pairs after any command, not just L, to reuse the last command letter — it works for M (whose extra pairs become implicit Ls, as covered earlier) just as much as for C, Q, and the rest.
  • No space needed before a negative number: M10-80 parses fine, since - is itself a valid number separator.
  • Chained decimals: M0.5.5 is read as 0.5 0.5 — the second . is unambiguous, so it's treated as the start of a new number.
  • A leading zero can be dropped: .5 works exactly like 0.5.
  • Flags can be jammed together: since large-arc-flag and sweep-flag are always single digits, A5 5 0 004 4 is valid — no separator is needed between 0, 0, and the coordinate that follows.

None of this is required reading to write paths by hand — but it's exactly why a design tool's export looks like an unbroken wall of characters, and now it's decodable instead of intimidating.

Production Notes: Optimization, Accessibility, and Tools

A few habits separate a path that merely works from one that's ready to ship:

  • Trim the precision. Design-tool exports routinely carry 6+ decimal places per coordinate — far more precision than a screen can render. Tools like SVGO collapse that down (commonly to 2–3 decimals), strip redundant commands, and can convert verbose absolute chains into shorter relative ones automatically.
  • Label icons for screen readers. A <path> alone is invisible to assistive tech. Give a meaningful icon a <title> element as its first child (<svg role="img"><title>Search</title><path .../></svg>), or mark purely decorative icons aria-hidden="true" so screen readers skip them entirely.
  • Reach for a visual tool before hand-typing complex data. Everything decoded in this post — commands, curves, arc flags — is exactly what a drag-and-drop path editor builds for you: drag points, watch the d string update live, and export straight to <path>, standalone SVG, or React.

Summary

While we used <rect> and <circle> in the previous post for simple geometry, you now have the power to create any custom, organic shape using a single <path>.

The <path> element is arguably the most efficient way to store complex visual data. While we rarely code massive paths by hand, understanding these commands allows you to:

  • Optimize: Clean up messy code exported from design tools.
  • Animate: Use CSS to animate the stroke-dashoffset or change the d attribute values via JavaScript.
  • Fix Layouts: Quickly adjust a coordinate without reopening a design app.

Getting ten interactive command demos, a full heart-shape decode, and the fill-rule, pathLength, and CSS path() sections to all click together took a fair share of iteration, and I hope it leaves you able to read — and write — a `d` string without flinching. Thanks for sticking with me through the whole alphabet of path commands — go draw something only you could have coded. ✏️

  • SVG Polygon vs Polyline: Differences, Fill Rules, and Use Cases

    Master SVG polygons, polylines, and fill rules.
    Learn the difference between SVG polygon and polyline: points syntax, fill-rule, accessibility, and real use cases like charts, icons, and UI shapes.
    Published at:
  • SVG Essentials: Mastering Shapes, Coordinates, and Styling

    Build sharp, scalable graphics with basic shapes.
    Learn how to build resolution-independent SVG graphics. Master the viewBox, coordinates, essential shapes, and CSS styling with fill, stroke, and hover animations.
    Published at:
  • Animating SVG: A Guide to CSS Transitions and Keyframes

    Bring static shapes to life with transitions and @keyframes.
    Learn how to animate SVG with CSS: transitions vs @keyframes, self-drawing lines, clip-path reveals, staggered delays, motion paths, shape morphing, and scroll-driven timelines.
    Published at: