Use shape() for responsive clipping

Noam Rosenthal
Noam Rosenthal

Published: April 8, 2025

The clip-path property lets you change the shape of an element by clipping to a circle, polygon, or even an SVG path. However, before Chrome 135 and Safari 18.4, you had to choose between responsive polygons, and more complex shapes that are not responsive using SVG paths. With the new shape() function, a clip-path can clip the element to a non-polygon shape which is also responsive.

Browser Support

  • Chrome: 135.
  • Edge: 135.
  • Firefox Technology Preview: supported.
  • Safari: 18.4.

Source

Create a flag shape

As an example, compare creating a flag shape with clip-path: path() and clip-path: shape().

A green flag shape with curved lines at the top and bottom.

A flag shape is not exactly a polygon, as its top and bottom borders are cubic Bézier curves rather than straight lines or rounded corners.

Create the flag with clip-path: path()

A shape like this flag can be represented using an SVG path:

.flag {
  clip-path: path(
    "M 0 20 \
     C 25 0 75 40 100 20 \
     V 80 \
     C 75 100 25 60 0 80 \
     z");
}

To break this down, an SVG path is a series of path commands:

  1. Move to 0, 20.
  2. Curve to 100, 20, using control points (25,0 and 75, 40).
  3. Vertical line to 80.
  4. Curve to 0, 80, using control points (75,100 and 25,50).
  5. Close the path (line to 0,20).

This draws a flag shape, but all the units are in pixels. SVG can scale those pixels to a view-box, but in a way that would always look like a geometric scale of the whole shape.

For example, if you wanted the whole rectangle to scale, but maintain the height and width of the curves 20px, SVG wouldn't be up to the task.

Create the flag with shape()

Compare the same result using shape(). The shape function accepts a series of commands, similar to the SVG path commands. However, these commands accept CSS lengths and percentages, in any CSS unit.

The following CSS converts the flag a shape() with percentage units:

.flag {
  clip-path: shape(from 0% 20%,
     curve to 100% 20% with 25% 0% / 75% 40%,
     vline to 80%,
     curve to 0% 80% with 75% 100% / 25% 60%,
     close
  );
}

Make it responsive

With the full range of CSS lengths available, you can pick which ones to use for each coordinate.

For example, to make the entire size of the flag scale by the element's size, but keep the height of the curve constant, you can do the following:

.flag {
  clip-path: shape(from 0% 20px,
     curve to 100% 20px with 25% 0% / 75% 40px,
     vline to calc(100% - 20px),
     curve to 0% calc(100% - 20px) 
           with 75% 100% / 25% calc(100% - 40px),
     close
  );
}

Add custom properties and animations

With the shape now defined in CSS, you can also use custom properties, to make it easy to manipulate the height:

.flag {
  --wave-height: 40px;
  clip-path: shape(
    from 0px var(--wave-height),
    curve to 100% var(--wave-height) 
          with 25% 0px / 75% calc(var(--wave-height) * 2),
    vline to calc(100% - var(--wave-height)),
    curve to 0 calc(100% - var(--wave-height))
          with 75% 100% / 25% calc(100% - var(--wave-height) * 2),
    close
  )
}

You can even animate the CSS property using the @property descriptor, and clamp it so that it doesn't overreach:

@property --animated-wave-height {
  syntax: "<length>";
  inherits: false;
  initial-value: 40px;
}

@keyframes curve {
  from { --animated-wave-height: 0px; }
  to { --animated-wave-height: 180px; }
}

.flag {
  width: 600px;
  height: 400px;
  background: green;
  animation: curve 1s infinite alternate;
  --wave-height: calc(min(var(--animated-wave-height, 40px), 40%));
  clip-path: shape(
    from 0px var(--wave-height),
    curve to 100% var(--wave-height)
          with 25% 0px / 75% calc(var(--wave-height) * 2),
    vline to calc(100% - var(--wave-height)),
    curve to 0 calc(100% - var(--wave-height)) 
          with 75% 100% / 25% calc(100% - var(--wave-height) * 2),
    close
  )
}

Try the demo

In Chrome 135 or Safari 18.4, you can see the animating flag shape created using clip-path: shape() in this CodePen demo.

Summary

clip-path: shape() lets you clip your element using arbitrary and responsive shapes, previously only possible using techniques like conic gradients or JavaScript-constructed SVG.

Check the specification for the full syntax.

At the moment, it only works for clip-path. In the future, we envision using this kind of shape for setting the shape of the element's border, which would unlock even more non-rectangular ways of expression.