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
The six basic SVG shape elements. Each is a convenience - all can be replicated with <path>.
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:
| Attribute | What it does |
|---|---|
fill | Interior colour (or none, or url(#gradient)). Default: black. |
fill-opacity | Opacity of the fill (0–1). |
stroke | Outline colour (or none). Default: none. |
stroke-width | Outline thickness. Default: 1. |
stroke-linecap | End cap shape: butt (default), round, square. |
stroke-linejoin | Corner shape where lines meet: miter (default), round, bevel. |
stroke-dasharray | Dash pattern: e.g. "5 3" = 5 on, 3 off. |
stroke-dashoffset | Offset into the dash pattern. Animating this creates "drawing" effects (the technique behind SVG line animations in Lesson 11). |
opacity | Whole-element opacity (fill + stroke together). |
transform | Transformation (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 / m | Move to | x y | Moves the pen without drawing |
L / l | Line to | x y | Draws a straight line to the point |
H / h | Horizontal line | x | Draws a horizontal line |
V / v | Vertical line | y | Draws a vertical line |
Z / z | Close path | (none) | Draws a line back to the starting point |
C / c | Cubic Bézier | x1 y1, x2 y2, x y | Curve with two control points |
S / s | Smooth cubic | x2 y2, x y | Continues previous cubic smoothly |
Q / q | Quadratic Bézier | x1 y1, x y | Curve with one control point |
T / t | Smooth quadratic | x y | Continues previous quadratic smoothly |
A / a | Arc | rx ry rotation large-arc sweep x y | Elliptical arc to a point |
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) -->
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:
- Control point 1 (x1, y1) - defines the direction and "force" the curve leaves the start point
- Control point 2 (x2, y2) - defines the direction and "force" the curve arrives at the end point
- End point (x, y) - where the curve ends (becomes the new current point)
<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) -->
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) -->
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.
"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 |
| Parametric | x = 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.
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.
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) | 1 | 2 | 2 + 0 control points |
| Quadratic | 2 | 3 | 2 + 1 control point |
| Cubic | 3 | 4 | 2 + 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
- At t = 0: B(0) = P0 (you're at the start)
- At t = 1: B(1) = P2 (you're at the end)
- At t = 0.5: the curve is nearest to (but not necessarily at) the control point
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
- At t = 0: B(0) = P0
- At t = 1: B(1) = P3
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:
S x2 y2, x y- smooth cubic: mirrors the previous C's second control point as the new first control pointT x y- smooth quadratic: mirrors the previous Q's control point as the new control point
<!-- 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.
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"/>
| Parameter | Meaning |
|---|---|
rx | Horizontal radius of the ellipse |
ry | Vertical radius of the ellipse |
x-rotation | Rotation of the ellipse in degrees (usually 0) |
large-arc-flag | 0 = smaller arc, 1 = larger arc |
sweep-flag | 0 = counter-clockwise, 1 = clockwise |
x y | End 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:
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:
- 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 - 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-flagandsweep-flag: they select which of the 4 possible ellipse centres (and therefore which arc) you want. - 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) - 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:
- large-arc-flag - 0: take the arc spanning less than 180°. 1: take the arc spanning more than 180°.
- sweep-flag - 0: trace counter-clockwise (decreasing θ). 1: trace clockwise (increasing θ).
Practical takeaway
You don't need to compute any of this by hand - the browser does it. But understanding the geometry explains why:
- You need two flags (4 possible arcs between any two points)
- If rx/ry are too small to reach the endpoint, the browser scales them up automatically
- A full circle is impossible (start = end means the midpoint calculation is undefined - infinite solutions)
- The
x-rotationparameter rotates the ellipse itself, not the arc's position
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:
- You rarely write path data by hand. Tools like Figma, Illustrator, and Inkscape export SVG with optimised path data. You'll read and tweak paths more often than write them from scratch.
- When you DO hand-write, you mostly need M, L, C, and Z. These four commands cover the vast majority of hand-authored paths. H, V, S, T, and A are nice-to-haves.
- Whitespace is flexible. Commas are optional separators. Multiple spaces are fine.
M10 10L50 10andM 10, 10 L 50, 10are identical. Use whatever's most readable. - Path data can be animated (with SMIL or the Web Animations API) - but only if the source and target paths have the same number and type of commands. You can morph
M..L..L..Zinto anotherM..L..L..Z, but not intoM..C..Z. - Relative commands make reusable shapes. A path written with all-relative commands (except the initial
M) can be repositioned just by changing theMcoordinate - the rest is offsets. - The
pathLengthattribute rescales dash calculations. Every shape element (not just<path>) supportspathLength. It tells the browser: "forstroke-dasharrayandstroke-dashoffset, treat this element as if it's this many units long" - regardless of the actual geometric length. SetpathLength="100"and a dasharray value of10means "10% of the shape." SetpathLength="1"and you can animatestroke-dashoffsetfrom 1 to 0 to draw any path without ever calculating its real length. This becomes crucial for stroke-drawing animations in Lesson 11.
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.
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.
| Attribute | Purpose |
|---|---|
marker-start | Marker at the first vertex |
marker-mid | Marker at every intermediate vertex |
marker-end | Marker at the last vertex |
markerWidth, markerHeight | Size of the marker's viewport |
refX, refY | Point in the marker that aligns with the vertex |
orient | auto (rotate to path tangent) or a fixed angle |
markerUnits | strokeWidth (scales with stroke) or userSpaceOnUse (fixed size) |
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.
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-radiusrx and rycornerroundQuestion 2
What's the difference between <polyline> and <polygon>?
Question 3
In <path d="M 10 10 L 50 10 L 50 50 Z">, what does the Z command do?
Hands-On Exercise
Practice these concepts by drawing these shapes with <path> commands:
- Draw a house using only line commands (
M,L,Z): a rectangular base with a triangular roof on top. Usefill="none"and a visible stroke so you can see the outline clearly. - 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 - 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>
d attribute. Lets you drag control points and see the path data update in real time.