Bézier Curves: Moving Beyond Straight Lines

The line commands covered previously give you the ability to draw any polygon or polyline, but they don’t let you escape the world of straight edges. The remaining path commands — quadratic and cubic Bézier curves, plus elliptical arcs — open up entirely new shape possibilities.

Bézier curves introduce the concept of control points. These are not points that get drawn; rather, they act as the “handles” that pull and shape the curve between two endpoints. The math behind plotting these curves is beyond this article’s scope, but SVG handles all the heavy lifting — you only need to supply the coordinates.

The most intuitive way to understand how control points shape a curve is to render the handles visually, similar to how vector graphics editors like Illustrator display them. The examples below use markers and animation to show each handle’s position and how adjusting the control point changes the curve itself.

Quadratic Bézier Curves: Q And T Commands

A quadratic Bézier has a single control point. The Q command takes two arguments: the control point and the end point:

const path = `M${start.x} ${start.y} Q${control.x} ${control.y} ${end.x} ${end.y}`;

After a M command to establish the start point, the Q command takes over. The start point, control point, and end point together define the curve’s trajectory.

See the Pen [SVG Path Quadratic Bézier Curve Visual [forked]](https://codepen.io/smashingmag/pen/LEVXLoJ) by Myriam.

See the Pen SVG Path Quadratic Bézier Curve Visual [forked] by Myriam.

Quadratic curves have a notable limitation: they remain “u” or “n” shaped no matter how you arrange the control point. They can be squished or stretched, but they can never curl back on themselves or form loops.

When chaining multiple quadratic curves together (creating what are called “splines”), there’s an additional command that simplifies the process. The T command only takes the end point as an argument, and it automatically reflects the previous control point through the end point of the preceding Q (or T command) to generate the new control point:

const path = `M${p1.x} ${p1.y} Q${cP.x} ${cP.y} ${p2.x} ${p2.y} T${p3.x} ${p3.y}`

For example, the top curve in the interactive demo below uses two Q commands with three control points total. The bottom curve shows how the same shape can be reproduced with one Q followed by a T command — the third control point in the first version turns out to be a perfect reflection of the second one.

See the Pen [SVG Path Quadratic Curve T Command [forked]](https://codepen.io/smashingmag/pen/vEOQJBM) by Myriam.

See the Pen SVG Path Quadratic Curve T Command [forked] by Myriam.

The benefit is that when you move the starting or ending points, the inferred control points follow along automatically, ensuring smooth connections:

See the Pen [SVG Path Quadratic Bézier Spline T Command Visual [forked]](https://codepen.io/smashingmag/pen/WbvYENx) by Myriam.

See the Pen SVG Path Quadratic Bézier Spline T Command Visual [forked] by Myriam.

Lowercase q and t use relative coordinates, just as with the line commands.

Cubic Bézier Curves: C And S Commands

Cubic Béziers work like their quadratic counterparts, but with two control points instead of one. The C command takes three arguments: the first control point, the second control point, and the end point:

const path = `M${p1.x} ${p1.y} C${cP1.x} ${cP1.y} ${cP2.x} ${cP2.y} ${p2.x} ${p2.y}`;

The order matters — you list the control points first, then the destination:

See the Pen [SVG Path Cubic Bézier Curve Animation [forked]](https://codepen.io/smashingmag/pen/EajOvaL) by Myriam.

See the Pen SVG Path Cubic Bézier Curve Animation [forked] by Myriam.

Unlike quadratic curves, cubic curves are “contortionists.” They can curl back on themselves, form loops, and even split the filled area into two separate regions — something a quadratic curve is incapable of doing.

For chaining cubic curves, the S command serves the same role that T serves for quadratics. The first control point is reflected from the previous curve’s second control point, and you only need to supply the new second control point and the end point:

const path = `    
  M ${p0.x} ${p0.y}
  C ${c0.x} ${c0.y} ${c1.x} ${c1.y} ${p1.x} ${p1.y}
  S ${c2.x} ${c2.y} ${p2.x} ${p2.y}
`;

The visual below makes this clear: the reflected handles are shown in green while the explicitly defined ones appear in red:

See the Pen [SVG Path Cubic Bézier Spline S Command Visual [forked]](https://codepen.io/smashingmag/pen/RNPqZPz) by Myriam.

See the Pen SVG Path Cubic Bézier Spline S Command Visual [forked] by Myriam.

The real advantage of using T and S is guaranteeing smooth spline connections. But even without them, you can keep a connection smooth manually — as long as your control points form a straight line through the shared endpoint. A kink in the handles produces a kink in the curve.

The Arc Command and Its Many Parameters

The final path command type is for arcs — sections of circles or ellipses. The A command has more parameters than any other in the SVG path vocabulary, which makes it the least approachable at first glance. It is also the key to drawing proper donut charts, so it is worth the effort to understand.

As with every other path command, a lowercase a signals relative coordinates. An arc path declaration looks like this:

const path = `M${start.x} ${start.y} A${radius.x} ${radius.y} ${xAxisRotation} ${largeArcFlag} ${sweepFlag} ${end.x} ${end.y}`;

The three parameters that tend to cause confusion are xAxisRotation, largeArcFlag, and sweepFlag:

  • xAxisRotation — the rotation of the underlying ellipse's axes, expressed in degrees.
  • largeArcFlag — a boolean value deciding whether the arc spans more than 180°.
  • sweepFlag — also a boolean, setting the drawing direction: clockwise or counter-clockwise.

The interaction between these parameters is easier to grasp visually.

See the Pen [SVG Path Arc Command Visuals [forked]](https://codepen.io/smashingmag/pen/GgJwvZR) by Myriam.

See the Pen SVG Path Arc Command Visuals [forked] by Myriam.

How the Radius Behaves

In the visual above, the top row shows overlapping ellipses while the bottom row stacks them. Both rows use identical radius.x and radius.y values; what changes is the distance between start and end points.

The stacking effect is not a feature but graceful error handling. The SVG specification says the following adjustments must be made for invalid elliptical arc parameters:

If the endpoint (x, y) of the segment is identical to the current point (e.g., the endpoint of the previous segment), then this is equivalent to omitting the elliptical arc segment entirely.

If either rx or ry is 0, then this arc is treated as a straight line segment (a "lineto") joining the endpoints.

If either rx or ry have negative signs, these are dropped; the absolute value is used instead.

If rx, ry and x-axis-rotation are such that there is no solution (basically, the ellipse is not big enough to reach from the current point to the new endpoint) then the ellipse is scaled up uniformly until there is exactly one solution (until the ellipse is just big enough).

The bottom row demonstrates that scaling behavior: the ellipse grows until it can connect the two points. The top row, with logical values, shows how arcs should actually be used. With workable ellipse dimensions and two distinct points, there are exactly four ways to draw the connecting arc — which is precisely what the two boolean flags select between.

Axis Rotation

An ellipse's axes can be tilted around its center. The xAxisRotation value expresses that tilt in degrees, relative to the SVG's horizontal direction. If the ellipse is a perfect circle, rotating it produces no visible difference in the arc itself.

The Sweep Flag

The sweepFlag determines arc direction. A value of 0 draws the arc clockwise; a value of 1 draws it counter-clockwise.

The Large Arc Flag

The largeArcFlag chooses between the shorter or longer portion of the ellipse. When the ellipse has been scaled up just enough to reach both endpoints, the arc spans exactly 180°.

Arcs usually demand more circular number-wrangling than feels comfortable. Trigonometric functions appear quickly, and the reliance on interdependent values means small changes ripple through the whole command. But once you accept the input requirements, arcs are wonderfully capable.

Putting Path Commands to Work

Path commands shine when used to illustrate data. Grids, boxes, and curves are quick to assemble once the syntax is familiar, and moving beyond them into custom chart shapes takes only incremental effort.

With a solid understanding of all path command types, rendering a wide variety of charts — or entirely different visualizations — is within reach. For example, the cubic-bezier behind a CSS transition-timing-function: ease; can be converted into an arc-based representation that an <animate> tag can consume.

See the Pen [CSS Cubic Beziers as SVG Animations & CSS Transition Comparisons [forked]](https://codepen.io/smashingmag/pen/gbpQxgp) by Myriam.

See the Pen CSS Cubic Beziers as SVG Animations & CSS Transition Comparisons [forked] by Myriam.

SVG is quirky, and the path element can appear as an intimidating string of symbols during code inspection. The underlying logic, once exposed, transforms that syntax into something straightforward and powerful.

For further learning, the MDN tutorial about paths is a concise reference. The svg-tutorial.com site visualizes SVG coding well, particularly its Arc Editor. For decoding an existing path without memorizing every parameter, the SVG Path Visualizer breaks down path data in a readable format.

Go forth and have fun playing in the matrix.