path()

John Rhea on

Get affordable and hassle-free WordPress hosting plans with Cloudways — start your free trial today.

The CSS path() function enables us to create complex paths, polygons and other shapes using SVG path command syntax.

There are three major uses for the path() function. You can use it with the clip-path property, the offset-path property, and to set the d property for a SVG path element (like other SVG presentation attributes, the d attribute can be set in CSS, though this particular use of path() isn’t supported in Safari).

Of these three, only using the d property directly draws a shape on screen. clip-path does not draw a shape directly, but cuts a shape out of another element. offset-path creates a shape that you can animate another element along.

Basic usage

We’ll start with the SVG d property, creating a speech bubble:

<svg viewBox="0 0 200 200">
  <path d="">
</svg>
path {
  fill: orchid;
  /* A speech bubble */
  d: path("M200 78c0 42-45 77-100 77-7 0-14 0-21-2l-44 47 12-57C18 129 0 104 0 78 0 35 45 0 100 0s100 35 100 78Z");
}

Next, we’ll cut that speech bubble out of another element using clip-path:

<div class="hello"></div>
.hello {
  width: 200px;
  height: 200px;
  background-color: aqua;
  clip-path: path("M200 78c0 42-45 77-100 77-7 0-14 0-21-2l-44 47 12-57C18 129 0 104 0 78 0 35 45 0 100 0s100 35 100 78Z");
}

We can also use the path() function on an offset-path to create an animation. Let’s animate the speech bubble along its own path:

.hello {
  width: 200px;
  height: 200px;
  background-color: aqua;
  /* Cuts the shape out of the element */
  clip-path: path("M200 78c0 42-45 77-100 77-7 0-14 0-21-2l-44 47 12-57C18 129 0 104 0 78 0 35 45 0 100 0s100 35 100 78Z");
  /* Defines the animated path */
  offset-path: path("M200 78c0 42-45 77-100 77-7 0-14 0-21-2l-44 47 12-57C18 129 0 104 0 78 0 35 45 0 100 0s100 35 100 78Z");
  offset-distance: 0;
  /* Applies the animation */  
  animation: speech 10s linear infinite;
}
  /* Defines the animation */
@keyframes speech {
  from { offset-distance: 0; }
  to { offset-distance: 100%; }
}

Note: We’ll use the clip-path property in examples because it has wider browser support than the d attribute and is much easier to visualize how the path() function works than with offset-path since that’s for motion. However, the path() function works the same with all three CSS properties.

Keywords

The path() function accepts two parameters: the fill rule and a path string.

Fill rule

The first keyword in the path() function (which was omitted from the above examples because it’s optional) is the “fill” rule. This tells the browser how to fill the shape with color. There are two options: nonzero and evenodd (the same as in SVG). In most shapes, they work the same, however, when a shape overlaps itself or has overlapping shapes, the algorithms differ on what they fill with color and what they don’t.

The algorithm descriptions below are included for completeness. You don’t need to understand how the algorithms work to use path(). Most of the time you can use the default value (nonzero) and it will work the way you’d expect. If it ever isn’t working the way you want, try switching to evenodd. And if that doesn’t work, then the fill rule isn’t the issue.

  • nonzero (default): The browser picks a point and draws a ray (a straight line) out of the shape. The browser also sets a direction along the shape’s path (basically, if it were being drawn, it chooses a direction the pen is moving in to complete the shape). It doesn’t really matter where it starts or which direction it begins in because the algorithm only cares about the ray crossing paths that move (or are drawn in) opposite directions. When the ray crosses part of the shape’s path, the browser increments or decrements a counter. If the ray crosses a part of the path moving in a clockwise direction, the browser increments; if the path is moving counterclockwise, the browser decrements. When the resulting counter is not zero, the point is inside the shape and within the fill. If the counter is zero, the point is outside.
  • evenodd: The browser picks a point and draws a ray (a straight line) from that point out of the shape (it does not care about the direction of the shape’s path). Every time the ray crosses a path of the shape it increments a counter. If the counter ends as an odd number, the point is considered inside the shape and within the fill. If the counter is even, the point is outside the shape.

If a fill rule is used it should be followed by a comma (,).

Path string

The path string is borrowed from the path element in SVG and can feel like a string of meaningless letters and numbers, but we’ll demystify them below (or at least help you see it’s worth bookmarking this page on the off-chance you really need to hand-edit a path string). If you need a more human-readable solution, make sure you look into the shape() function.

Uppercase letters vs. Lowercase letters

In the next section, we will discuss specific path commands for drawing lines, arcs, and curves. That syntax will use a particular path command in either an uppercase letter or a lowercase letter, plus the coordinates associated with that path command.

M10,10

The difference between M and m is that coordinates following a capital letter command are absolute coordinates (i.e., move to this coordinate) versus a lowercase letter being followed by relative coordinates (i.e, move by this amount).

Path commands

All path commands take the previous command’s endpoint as the starting point. The first command uses the upper-left corner of the element as the starting point.

For the sake of simplicity, I will only use lowercase letters in the path commands below, but capital letters are equally usable in all of the commands. Also, while I will often use the word “draw” to describe how a path command works, it’s important to remember that these commands do not show anything on the screen when using clip-path, but rather are defining the shape of the cookie cutter that will be cutting out the shape from an element.

Generally, when a comma separates values they are the X and Y coordinates of a point. Spaces separate multiple points from each other and from other kinds of values such as angles or flags.

Move (M/m)

The move command is like picking up the pen while drawing and moving to a new location allowing for multiple, unconnected shapes to be created using a single path() function.

m100,150

Line (L/l)

The line command draws a line from the starting point to the provided coordinates.

l100,-50

Horizontal line (H/h)

The horizontal line command draws a horizontal line from the starting point to the provided X coordinate.

h100

Vertical line (V/v)

The vertical line command draws a vertical line from the starting point to the provided Y coordinate.

v-50

Cubic Bézier Curve (C/c)

The Cubic Bézier Curve command draws a Bézier curve from the starting point to the provided coordinates. The curvature is defined by the control points. The first point below is the control point for the starting point (20,-20), the second point is the control point for the end point (50,-20) and the third point is the end point of the curve (100,40).

c20,-20 50,-20 100,40

Quadratic Bézier Curve (Q/q)

The Quadratic Curve command is similar to the Cubic Bézier Curve command but uses the same control point (10,-40) for both the starting point and ending point (100,-20) of the curve.

q10,-40 100,-20

Smooth Cubic Bézier Curve (S/s)

The Smooth Cubic Bézier Curve command draws a Bézier curve from the starting point to the provided coordinates, but uses mirrored control points to try to make smooth transitions from one curve to the next. The smooth command mirrors the control point from the previous path command as the control point for the curve’s starting point. Then it takes a control point for the end point (50,-40) and the end point itself (100,20).

s50,-40 100,20

Smooth Quadratic Bézier Curve (T/t)

The Smooth Quadratic Bézier Curve command draws a quadratic Bézier curve using the mirrored control point from the starting point as the control point for both the starting point and the end point. Thus, it only takes the end point (100,-20).

t100,-20

Elliptical Arc Curve (A/a)

The Elliptical Arc Curve command draws a portion of an ellipse between the starting point and the ending point. It takes seven values. All of the values — except the last two — are space-separated (since the last two are the end coordinates, they are comma separated).

The first value is the X radius of the ellipse followed by the Y radius of the ellipse. Then we have an angle relative to the X axis for how much the ellipse should be rotated through the points.

Having set this information, there are still four possible arcs that this information could pull from an ellipse. As such, the Elliptical Arc Curve command has two flag features that let you determine which portion of the ellipse you want to use.

First, we have the “large-arc-flag” that determines whether you want the larger portion of the ellipse defined or the smaller portion. If you stick your starting and ending points on an ellipse and draw a straight line that intersects both points, you’ll cut the ellipse in two segments. Do you want the larger portion (1) or the smaller portion (0)?

Second we have the “sweep-flag” that determines whether you want the arc to go in a clockwise (1) or counterclockwise (0) direction. It’s easiest to think of this by putting the start and end point on the face of an analog clock. If you want it to sweep in the same direction as time, then you want clockwise. If you want it to sweep against time, then you want counter-clockwise.

Here’s a diagram from the spec (this is from the shape() specification, but it illustrates the equivalent arc command for path()):

a50 30 20 0 0 100,20
a20 30 50 0 1 100,-30

Close (Z/z)

The Close command does what you might think: it closes the shape, drawing a straight line between the endpoint of the last path command and the starting point.

z

path() vs. shape()

There are two big differences between the path() function and the shape() function:

  1. The path() function requires you to know SVG path commands that are written more for brevity than for human readability. The shape() function provides much more human-readable code that requires less mental decoding to read and understand.
  2. The path() function implicitly only allows for pixel measurement. As such, it can be difficult to create shapes that are fully responsive or otherwise scale smartly. The shape() function allows you to use any CSS unit and also the results of any math function to create both dynamic and responsive shapes.

It should also be said that because of its dynamic and responsive abilities, you can do things with shape() that you just can’t do with path() unless you have some JavaScript dynamically updating path()’s values.

Creating shapes

While I recommend learning the path commands so you can create and manipulate shapes, they are a lot of work. It’s also really hard to visualize what you’re working on when you can’t actually see it until you’ve competed the shape (or at least enough to have a closed shape). For that reason, I suggest starting by creating a vector shape in a graphics editing program that can output to SVG, particularly for complex shapes.

Once you output the SVG code, you can grab the path string from the d attribute of the SVG path element and plop it in (in quotations) into the path() function.

Your mileage may vary, but I almost never handwrite my path strings. However, I do find it helpful now and again to have a general understanding of how they work for when things are just a little bit off from what I want or when performance requires the smallest possible file size.

Browser support

path() is supported in modern browsers (Chrome, Firefox, Safari, Edge) and has been since 2020.

Specification

The CSS path() function is defined in the CSS Shapes Module Level 1 specification, which became a Candidate Recommendation on June 12, 2025. That means it integrates changes from a previous Candidate Recommendation (Draft or Snapshot) to allow for review and for ease of reference to the integrated specification, but those changes have not undergone a formal review.