← Back to Index

Lesson 3

Basic Shapes & Path Commands

With the coordinate system and transforms established, this lesson covers what actually gets drawn in that space: SVG's built-in shape elements, and the all-powerful <path> element that can describe any 2D shape.

1. The Basic Shape Elements

SVG provides six convenience elements for common geometric shapes. Each is a specialised shorthand - they exist to save you from writing verbose path data for shapes you use constantly.

<rect> - Rectangle

The workhorse shape. Position with x/y (top-left corner), size with width/height, round corners with rx/ry:

<rect x="20" y="20" width="120" height="80" rx="10" ry="10"
      fill="#61afef" stroke="#528bcc" stroke-width="2"/>

If only rx is specified, ry defaults to the same value (and vice versa). Set both to create elliptical corners.

<circle> - Circle

Defined by its centre (cx, cy) and radius (r):

<circle cx="80" cy="80" r="50"
      fill="#e06c75" stroke="#b04950" stroke-width="2"/>

<ellipse> - Ellipse

Like a circle but with independent horizontal (rx) and vertical (ry) radii:

<ellipse cx="100" cy="60" rx="80" ry="40"
      fill="#e5c07b" stroke="#b8993f" stroke-width="2"/>

<line> - Line Segment

A straight line from (x1, y1) to (x2, y2). Lines have no fill - only stroke is visible:

<line x1="10" y1="10" x2="180" y2="100"
      stroke="#98c379" stroke-width="3" stroke-linecap="round"/>

<polyline> - Connected Line Segments

Multiple connected points. The points attribute takes space-separated x,y pairs. The shape is not automatically closed:

<polyline points="20,80 60,20 100,60 140,10 180,50"
      fill="none" stroke="#c678dd" stroke-width="3"
      stroke-linejoin="round" stroke-linecap="round"/>

<polygon> - Closed Polyline

Same as polyline, but automatically draws a closing line from the last point back to the first:

<polygon points="100,10 150,80 50,80"
      fill="#56b6c2" stroke="#3d8f99" stroke-width="2"/>

All six shapes at a glance

<rect> rx="10" ry="10" <circle> r="50" <ellipse> rx="80" ry="40" <line> <polyline> <polygon>

The six basic SVG shape elements. Each is a convenience - all can be replicated with <path>.

Shapes are convenience shortcuts

Every basic shape element can also be expressed as a <path> - the shapes just save you from writing path commands for common cases. A <circle cx="50" cy="50" r="30"/> is cleaner than the equivalent path with two arc commands. But under the hood, the browser converts them all to paths for rendering.

Shared attributes (apply to all shapes)

In addition to their geometry-specific attributes, all shape elements accept these painting and styling attributes:

AttributeWhat it does
fillInterior colour (or none, or url(#gradient)). Default: black.
fill-opacityOpacity of the fill (0–1).
strokeOutline colour (or none). Default: none.
stroke-widthOutline thickness. Default: 1.
stroke-linecapEnd cap shape: butt (default), round, square.
stroke-linejoinCorner shape where lines meet: miter (default), round, bevel.
stroke-dasharrayDash pattern: e.g. "5 3" = 5 on, 3 off.
stroke-dashoffsetOffset into the dash pattern. Animating this creates "drawing" effects (the technique behind SVG line animations in Lesson 11).
opacityWhole-element opacity (fill + stroke together).
transformTransformation (translate, rotate, scale, etc.).

These are all presentation attributes - they can also be set via CSS. We'll cover the interplay between attributes and CSS in Lesson 4.

2. The <path> Element

The <path> element is the most powerful drawing tool in SVG. It can reproduce anything the basic shapes can draw, plus arbitrary curves, complex outlines, and irregular forms. Its power lives entirely in a single attribute: d (for "data").

The d attribute uses a compact mini-language of single-letter commands followed by numeric parameters. Think of it as instructions for moving a pen across the canvas:

<path d="M 10 10 L 90 10 L 90 90 L 10 90 Z"
      fill="#61afef" stroke="#528bcc" stroke-width="2"/>
<!-- Draws a square: move to (10,10), line to each corner, close -->

Command overview

Command Name Parameters What it does
M / mMove tox yMoves the pen without drawing
L / lLine tox yDraws a straight line to the point
H / hHorizontal linexDraws a horizontal line
V / vVertical lineyDraws a vertical line
Z / zClose path(none)Draws a line back to the starting point
C / cCubic Bézierx1 y1, x2 y2, x yCurve with two control points
S / sSmooth cubicx2 y2, x yContinues previous cubic smoothly
Q / qQuadratic Bézierx1 y1, x yCurve with one control point
T / tSmooth quadraticx yContinues previous quadratic smoothly
A / aArcrx ry rotation large-arc sweep x yElliptical arc to a point
Uppercase = absolute, lowercase = relative

Uppercase commands use absolute coordinates (position within the element's current coordinate system - accounting for any viewBox and parent transforms). Lowercase commands use relative coordinates (offset from the current pen position). Both produce identical visual results - relative is often more convenient for reusable shapes because the path doesn't depend on where it's placed.

3. Line Commands in Practice

The most common path operations are the line commands: M, L, H, V, and Z. Let's draw a house using only these:

<path d="M 50 120
         L 50 200
         L 200 200
         L 200 120
         L 125 50
         Z"
      fill="none" stroke="#e5c07b" stroke-width="3"
      stroke-linejoin="round"/>
<!-- M: start at left roof point
     L 50 200: down to bottom-left
     L 200 200: across to bottom-right
     L 200 120: up to right roof point
     L 125 50: up to roof peak
     Z: close back to start (left roof point) -->
M 50,120 L 50,200 L 200,200 L 200,120 L 125,50 Same house with H and V shortcuts M 300 120 V 200 H 450 V 120 L 375 50 Z

Left: house drawn with M and L commands only. Right: same shape using H (horizontal) and V (vertical) shortcuts where applicable - saves specifying the coordinate that doesn't change.

H and V are just shortcuts: H 200 means "draw a horizontal line to x=200" (y stays the same). V 120 means "draw a vertical line to y=120" (x stays the same). They save typing when you know one coordinate isn't changing.

4. Bézier Curves

Lines can only get you so far. For smooth, organic shapes you need curves. SVG paths support two types of Bézier curves: cubic (two control points) and quadratic (one control point).

Cubic Bézier - C command

The cubic Bézier is the most common curve in vector graphics. It takes two control points plus an endpoint:

<path d="M 30 150 C 30 50, 170 50, 170 150"
      fill="none" stroke="#c678dd" stroke-width="3"/>
<!-- Start at (30,150)
     Control 1: (30, 50)  -  pulls the curve up from start
     Control 2: (170, 50)  -  pulls the curve up toward end
     End: (170, 150) -->
Start End CP1 (80, 50) CP2 (250, 50) Cubic Bézier: C x1 y1, x2 y2, x y Red dashed lines show how control points "pull" the curve Two cubics chained = S-curve

A cubic Bézier with its control points (red) visible. The dashed lines show the "handles" - control points define the tangent direction at each endpoint. Right: two cubics chained to form an S-curve.

Quadratic Bézier - Q command

A simpler curve with only one control point. The curve leaves the start point and arrives at the end point both "aiming" toward this single shared control point:

<path d="M 30 150 Q 100 30, 170 150"
      fill="none" stroke="#56b6c2" stroke-width="3"/>
<!-- Start: (30, 150)
     Control: (100, 30)  -  the single shared control point
     End: (170, 150) -->
Start End CP (200, 20) shared by both ends Quadratic: Q x1 y1, x y - one control point, symmetric curve

A quadratic Bézier with its single control point (red). Both dashed lines show the tangent direction - the curve leaves the start aiming at CP and arrives at the end coming from CP. The result is always symmetric.

Quadratic curves are inherently symmetric - the curve's shape is the same on both sides of the control point. Cubic curves can be asymmetric because each endpoint has its own independent control point.

The maths behind Bézier curves

Both are parametric curves - the position at any point along the curve is a function of a single parameter t, where t goes from 0 (start) to 1 (end).

t is the independent variable - a progress value from 0 to 1, like a slider. Both x and y are separate functions of t (that's what "parametric" means - instead of y = f(x), you have x = f(t) and y = g(t)). The control points define the shape of the curve; t asks "at this progress level, where am I on that shape?" The browser sweeps t from 0 to 1 in tiny increments, computing the x,y position at each step, and connects the dots to render the curve. You never specify t yourself - SVG handles it internally.

Because x and y are both functions of t (rather than y being a function of x), Bézier curves can loop, go vertical, or form any shape - they're not constrained by "one y per x" like a regular function graph. Note: t = 0.5 is not necessarily the geometric midpoint of the curve - it's halfway through the parametric equation, which may not be the visual centre if the curve is asymmetric.

What does "parametric" actually mean?

"Parametric" means the coordinates (x, y) are both expressed as functions of a separate, independent variable (the "parameter") rather than directly in terms of each other:

Form Equation(s) Constraint
Standard (non-parametric)y = f(x)One y per x - can't go vertical, can't loop
Parametricx = f(t), y = g(t)No geometric constraints - any shape possible

The word "parameter" here just means "the variable you sweep through." It's like a clock ticking from 0 to 1 - at each tick, you have an (x, y) position. The parameter t doesn't represent space or distance; it just progresses, and the equations produce coordinates.

Analogy: describing a walk through a city. Non-parametric: "When you're at street number x, your north-south position is y." (One position per street - can't describe backtracking or circles.) Parametric: "At time t=0 you're at this intersection. At t=0.3 you're here. At t=1 you arrive." (Can describe any route.)

This is why all vector graphics systems (SVG, PostScript, font outlines) use parametric curves - they can express any shape without restriction.

Can you convert parametric to y = f(x)?

Sometimes - if x increases monotonically. For example, with P0=(0,0), P1=(50,100), P2=(100,0):

x(t) = 100t          → so t = x/100
y(t) = 200t - 200t^2  → substitute t = x/100:
y = 2x - 0.02x^2      ← a regular parabola!

But this only works because x goes steadily from 0 to 100. If the curve loops back (x decreases then increases again), you get multiple y values for the same x - it fails the vertical line test and can't be expressed as y = f(x). That's exactly when the parametric form is essential.

Why "quadratic" and "cubic"?

The names refer to the degree of the polynomial, not to counts of things. "Quadratic" means the equation involves t² (squaring) - from Latin quadratus (squared). "Cubic" means the equation involves t³ (cubing). The number of total points is always degree + 1:

Name Degree Total points = Start + End + Control
Linear (a straight line)122 + 0 control points
Quadratic232 + 1 control point
Cubic342 + 2 control points

"Quad" here means "squared" (like quadratic equation in school), not "four." The Latin connection: a square has four sides, its area is x² - so quadratus came to mean "squared."

Quadratic Bézier (Q) - three points: P0 (start), P1 (control), P2 (end)

B(t) = (1-t)^2 * P0  +  2(1-t)t * P1  +  t^2 * P2     where t is 0 to 1

The derivative (tangent direction) at the endpoints:

B'(0) = 2(P1 - P0)     -  curve leaves P0 heading toward P1
B'(1) = 2(P2 - P1)     -  curve arrives at P2 coming from direction of P1

This is why the control point defines the tangent at both endpoints - the curve is pulled toward it from both sides. It's a weighted average of the three points where the weights shift as t progresses.

Worked example: computing a point on a quadratic Bézier

Given P0 = (20, 100), P1 = (100, 10), P2 = (180, 100) - where is the curve at t = 0.5?

Step 1: compute the three weights from t = 0.5

  (1-t)^2  = (0.5)^2     = 0.25
  2(1-t)t  = 2(0.5)(0.5) = 0.50
  t^2      = (0.5)^2     = 0.25

Step 2: apply weights to x coordinates of each point

  x = 0.25 * 20  +  0.50 * 100  +  0.25 * 180
    = 5           +  50           +  45
    = 100

Step 3: apply same weights to y coordinates of each point

  y = 0.25 * 100  +  0.50 * 10  +  0.25 * 100
    = 25           +  5           +  25
    = 55

Result: at t = 0.5, the curve passes through (100, 55)

The same weights apply to both x and y independently. The browser does this for hundreds of t values between 0 and 1, plots each resulting (x, y) point, and connects them to render the smooth curve. You compute the weights once from t, then multiply each weight by the corresponding point's coordinate.

But which quadratic? There are infinitely many!

A generic quadratic equation y = ax^2 + bx + c has three unknowns - infinite possibilities. But a quadratic Bézier has three points (P0, P1, P2), each with an x and y - that's 6 numbers. Those 6 numbers fully determine which specific quadratic polynomial you get. There's exactly one curve that satisfies the constraints imposed by those points.

The control points select the specific curve from the infinite family. Different points → different curve. Same formula structure, different inputs → unique output. It's like asking "which parabola is y = 3x² − 2x + 5?" - the coefficients (3, −2, 5) pin it down. In Bézier curves, the points play the role of the coefficients, just expressed as geometric positions (which you can see and drag) rather than abstract numbers.

Same applies to cubic: 4 points × 2 coordinates = 8 numbers, which fully determine one specific cubic curve out of the infinite family.

Cubic Bézier (C) - four points: P0 (start), P1 (control 1), P2 (control 2), P3 (end)

B(t) = (1-t)^3 * P0  +  3(1-t)^2 * t * P1  +  3(1-t) * t^2 * P2  +  t^3 * P3     where t is 0 to 1
B'(0) = 3(P1 - P0)     -  curve leaves P0 toward P1
B'(1) = 3(P3 - P2)     -  curve arrives at P3 from direction of P2

Each endpoint has its own independent control point, so the departure and arrival directions are decoupled - this is what gives cubics their asymmetric flexibility over quadratics.

Practical implication: control point distance = "force"

The distance from an endpoint to its control point affects how aggressively the curve shoots in that direction. A far control point = strong pull (the curve travels a long way before bending). A close control point = gentle departure. Design tools visualise this as "handles" - handle length is the force magnitude, handle angle is the tangent direction.

Why S and T commands give smooth joins

The smooth commands (S for cubic, T for quadratic) mirror the previous curve's final control point across the junction:

reflected_CP = 2·P_junction - CP_previous_end

This guarantees C¹ continuity - the tangent direction and magnitude match at the join, so the transition is perfectly smooth with no visible kink.

Smooth continuation - S and T commands

When chaining multiple curves, you often want a smooth junction (no visible "corner"). The S and T commands automatically mirror the previous curve's final control point to create seamless continuity:

<!-- Smooth cubic: S mirrors previous CP2 -->
<path d="M 30 100 C 30 30, 100 30, 100 100 S 170 170, 170 100"
      fill="none" stroke="#c678dd" stroke-width="3"/>

<!-- Smooth quadratic: T mirrors previous CP -->
<path d="M 30 100 Q 80 30, 130 100 T 230 100"
      fill="none" stroke="#56b6c2" stroke-width="3"/>

The "smooth" commands save you from calculating the mirrored control point yourself. They're essential for drawing smooth, multi-segment curves.

Ad

5. The Arc Command

The arc command (A) is the most complex path command. It draws an elliptical arc from the current point to a new endpoint, and requires 7 parameters:

A rx ry x-rotation large-arc-flag sweep-flag x y

<!-- Example: -->
<path d="M 50 100 A 50 30 0 0 1 200 100"
      fill="none" stroke="#e5c07b" stroke-width="3"/>
ParameterMeaning
rxHorizontal radius of the ellipse
ryVertical radius of the ellipse
x-rotationRotation of the ellipse in degrees (usually 0)
large-arc-flag0 = smaller arc, 1 = larger arc
sweep-flag0 = counter-clockwise, 1 = clockwise
x yEnd point of the arc

The two flags - 4 possible arcs

Given any two points and an ellipse size, there are 4 possible arcs that connect them. The large-arc-flag and sweep-flag select which one you want:

large-arc=0, sweep=0 Small arc, counter-clockwise large-arc=0, sweep=1 Small arc, clockwise large-arc=1, sweep=0 Large arc, counter-clockwise large-arc=1, sweep=1 Large arc, clockwise

All four arc variants between the same two points (green dots), with rx="80" ry="60". The two flags select which of the four possible arcs to draw.

The maths behind arcs

Unlike Béziers (which are parametric polynomial curves), arcs trace a section of an ellipse. The maths is more involved because SVG specifies arcs by endpoint rather than by centre - so the browser must solve for the ellipse centre given the constraints.

What you specify (endpoint parameterisation)

A rx ry x-rotation large-arc-flag sweep-flag x₂ y₂

Given: current point (x₁, y₁) and endpoint (x₂, y₂)
       ellipse radii rx, ry
       rotation angle φ (x-rotation, in degrees)

What the browser computes (centre parameterisation)

The browser converts your endpoint parameters to a centre form. The key steps:

  1. Rotate into the ellipse's local space: Apply −φ rotation to move the problem into an axis-aligned frame.
    x₁' = cos(φ)·(x₁-x₂)/2 + sin(φ)·(y₁-y₂)/2
    y₁' = -sin(φ)·(x₁-x₂)/2 + cos(φ)·(y₁-y₂)/2
  2. Find the centre of the ellipse in the rotated frame. This involves solving:
    cx' = ±√[ (rx²·ry² - rx²·y₁'² - ry²·x₁'²) / (rx²·y₁'² + ry²·x₁'²) ] · (rx·y₁'/ry)
    cy' = ∓√[...] · (ry·x₁'/rx)

    The ± sign is chosen based on the large-arc-flag and sweep-flag: they select which of the 4 possible ellipse centres (and therefore which arc) you want.

  3. Compute start and sweep angles (θ₁ and Δθ) using atan2:
    θ₁ = atan2( (y₁' - cy') / ry, (x₁' - cx') / rx )
    θ₂ = atan2( (-y₁' - cy') / ry, (-x₁' - cx') / rx )
    Δθ = θ₂ - θ₁  (adjusted for sweep direction)
  4. Trace the ellipse parametrically from θ₁ to θ₁ + Δθ:
    x(θ) = cx + rx·cos(θ)·cos(φ) - ry·sin(θ)·sin(φ)
    y(θ) = cy + rx·cos(θ)·sin(φ) + ry·sin(θ)·cos(φ)

Why the flags exist

Given two points and an ellipse size, there are always two possible ellipses that pass through both points (one "above," one "below"). On each ellipse, there are two arcs connecting the points (the short way and the long way). That's 2 × 2 = 4 possible arcs. The two flags select which one:

Practical takeaway

You don't need to compute any of this by hand - the browser does it. But understanding the geometry explains why:

Arcs can't draw a full circle

The start and end points of an arc cannot be identical - if they are, the arc degenerates to nothing. To draw a full circle with arcs, use two semicircular arcs back-to-back. Or just use <circle> - that's what it's for.

6. Practical Tips

Some real-world wisdom about working with path data:

Debugging paths

If a path looks wrong, add fill="none" and a visible stroke first. Fill can obscure path problems by filling "inside" overlapping segments. Once the outline looks right, add fill back.

How curves get made in practice

Almost nobody calculates Bézier control points or arc parameters from first principles. The realistic workflow split:

  • ~80% design tool export - draw visually in Figma/Illustrator/Inkscape, export SVG. The tool generates path data.
  • ~15% interactive editors - use tools like nan.fyi/svg-paths or SVG Path Editor to drag points and see path data update live. Good for hand-tweaking or building specific curves.
  • ~5% computed from formulae - only when generating shapes programmatically (data visualisation, dynamic animation paths, algorithmic art). Then you use the Bézier/arc maths in code.

Understanding the maths means you can predict, debug, and tweak intelligently - not that you need to compute coordinates by hand. Know why it works; let tools do the arithmetic.

7. Markers

A <marker> is a small graphic that attaches to the vertices of a path, polyline, polygon, or line. Common uses: arrowheads, dots at data points, decorative endpoints.

<svg viewBox="0 0 400 120" xmlns="http://www.w3.org/2000/svg">
  <defs>
    <marker id="arrow" markerWidth="10" markerHeight="10"
            refX="9" refY="3" orient="auto" markerUnits="strokeWidth">
      <path d="M0,0 L0,6 L9,3 Z" fill="coral"/>
    </marker>
    <marker id="start-square" markerWidth="8" markerHeight="8"
            refX="4" refY="4">
      <rect width="8" height="8" fill="#98c379"/>
    </marker>
    <marker id="mid-dot" markerWidth="12" markerHeight="6"
            refX="6" refY="3" orient="auto">
      <ellipse cx="6" cy="3" rx="6" ry="3" fill="#61afef"/>
    </marker>
    <marker id="end-diamond" markerWidth="10" markerHeight="10"
            refX="9" refY="3" orient="auto">
      <path d="M0,0 L0,6 L9,3 Z" fill="#e5c07b"/>
    </marker>
  </defs>

  <!-- Line with arrowhead -->
  <line x1="30" y1="40" x2="370" y2="40"
        stroke="coral" stroke-width="2"
        marker-end="url(#arrow)"/>

  <!-- Polyline with different markers at start, mid, end -->
  <polyline points="30,90 130,70 230,100 330,75 370,90"
            fill="none" stroke="#61afef" stroke-width="2"
            marker-start="url(#start-square)"
            marker-mid="url(#mid-dot)"
            marker-end="url(#end-diamond)"/>
</svg>

Top: line with arrowhead (marker-end). Bottom: polyline with a green square at start, blue ellipses at mid vertices (notice how they rotate to align with the line direction thanks to orient="auto"), and a yellow arrowhead at end.

AttributePurpose
marker-startMarker at the first vertex
marker-midMarker at every intermediate vertex
marker-endMarker at the last vertex
markerWidth, markerHeightSize of the marker's viewport
refX, refYPoint in the marker that aligns with the vertex
orientauto (rotate to path tangent) or a fixed angle
markerUnitsstrokeWidth (scales with stroke) or userSpaceOnUse (fixed size)
refX/refY = the pin point

refX and refY define which point in the marker graphic aligns with the path vertex. Think of it as a pin: the marker is a small image, and refX/refY is where the pin goes through it. For an arrowhead whose tip is at (9, 3): set refX="9" refY="3" so the tip sits on the vertex. For a circle centred at (3, 3): set refX="3" refY="3" so the dot centres on the vertex. Then orient="auto" rotates the entire marker around that pin point to face the path direction.

orient="auto" rotates to the path tangent

Just like rotate="auto" on <animateMotion> (which you'll meet in Lesson 10), orient="auto" rotates the marker to face the direction of the line at that vertex. This is why arrowheads point the right way on curved paths without any manual rotation.

Quiz: Check Your Understanding

Question 1

Which attribute on <rect> creates rounded corners?

border-radius
rx and ry
corner
round

Question 2

What's the difference between <polyline> and <polygon>?

Polyline supports curves, polygon only supports straight lines
Polygon requires a fill, polyline does not
Polygon automatically closes the shape (connects last point to first); polyline does not
They are identical - polygon is just an alias

Question 3

In <path d="M 10 10 L 50 10 L 50 50 Z">, what does the Z command do?

Resets the pen to (0, 0)
Ends the path without drawing anything
Draws a straight line back to the M starting point (10, 10), closing the shape
Fills the interior of the path with the current colour

Hands-On Exercise

Practice these concepts by drawing these shapes with <path> commands:

  1. Draw a house using only line commands (M, L, Z): a rectangular base with a triangular roof on top. Use fill="none" and a visible stroke so you can see the outline clearly.
  2. Draw a heart shape using two cubic Bézier curves. Start at the bottom point, curve up-left to the top-left lobe, then curve from there to the bottom point again for the right lobe. Hint: the path is M center-bottom C ... top-left C ... Z
  3. Draw a circle using two arc commands - two semicircles. Move to the leftmost point, arc to the rightmost point (half circle), then arc back to the start (other half). This demonstrates why <circle> exists as a convenience.
<svg width="500" height="200" viewBox="0 0 500 200">
  <!-- 1. House with line commands -->
  <path d="M 30 150 L 30 80 L 80 80 L 80 150 Z
            M 30 80 L 55 50 L 80 80"
    fill="none" stroke="#e5c07b" stroke-width="2"
    stroke-linejoin="round"/>

  <!-- 2. Heart with cubic Beziers -->
  <path d="M 200 140
           C 200 140, 170 80, 200 80
           C 230 80, 230 110, 200 140
           M 200 140
           C 200 140, 230 80, 200 80"
    fill="#e06c75" stroke="none"/>
  <!-- (Simplified  -  experiment with control points!) -->

  <!-- 3. Circle with two arcs -->
  <path d="M 350 100 A 40 40 0 1 1 430 100
                      A 40 40 0 1 1 350 100"
    fill="none" stroke="#61afef" stroke-width="2"/>
</svg>