clip-path shape(): Curvy Paths Directly in CSS Instead of SVG Path Strings
AI generated
{ }
@
CSS · Shapes · Layout · Graphics
clip-path: shape()
Curvy paths directly in CSS without writing a single SVG path string

The shape() function extends clip-path with real curve commands like curve to, arc to and smooth to, written in readable CSS syntax. Where only an external SVG or a rough polygon approximation used to help, organic, curvy shapes now happen without a build step and without a path editor.

13 min read shape() · curve to · arc to Chrome · Edge · Firefox (current)

1. What the shape() function is and which problem it solves

Until recently, clip-path essentially knew two ways to describe a shape: straight lines through polygon(), or a complete, externally referenced SVG file with a <clipPath> element. For anything needing curves, arcs, or soft transitions, the only option was a detour through SVG, often generated in a design tool and then copied as a path string that is barely readable for humans and even harder to adjust by hand.

shape() closes exactly that gap by introducing real curve commands directly as a CSS function value. Instead of a cryptic SVG d attribute like M10 10 C 20 20, 40 20, 50 10, there is now a readable syntax like curve to 50px 10px with 20px 20px that can be read and understood right in the stylesheet, and combined with CSS values like calc() or custom properties, which is simply not possible with an SVG path string.

2. Basic syntax: from, line to, curve to and close

A shape() declaration always starts with from <start-point> and then lists a sequence of commands that draw a path from that point. line to draws a straight line to the given point, exactly like a vertex in polygon(), while curve to <end-point> with <control-point> draws a quadratic, or with two control points a cubic, Bézier curve. The close command at the end closes the path back to the start point, exactly like Z in SVG.

All coordinates accept the usual CSS length units, percentage values relative to the element's reference box, and even calc() expressions, which makes it possible to describe a shape with a fixed border width of, say, 20 pixels, independent of the element's actual size. That is an advantage a static SVG path string does not offer by nature, because its coordinates get baked in at export time.


/* A triangle with a rounded tip */
.rounded-triangle {
  clip-path: shape(
    from 50% 0%,
    line to 100% 100%,
    curve to 0% 100% with 50% 130%,
    close
  );
}

3. shape() vs. polygon(): which tool for which job

polygon() remains the right choice for anything that genuinely consists of straight edges, such as a chevron, an arrow, or a diagonally cut hero area. The syntax is compact, every point is a simple coordinate, and there is no reason to reach for curve commands that would only end up describing a straight line anyway.

But once an edge is actually meant to look round or organic, the difference becomes clear: recreating a round shape with polygon() means breaking it into many small straight segments, which either looks jagged or requires a large number of coordinate pairs that become nearly unmaintainable by hand. shape() describes the same rounding with a single curve to or arc to command, with exactly the same rendering result but far more readable source.

4. Building organic shapes: combining several curve-to commands

For a freely formed, blob like area, several curve to commands line up one after another, with each control point determining how strongly the curve bulges outward or inward in that section. The practical workflow is usually iterative: set rough vertex points like a polygon first, then replace line to with curve to one at a time and drag the control points live in the browser DevTools shape editor until the silhouette looks organic.

One advantage over an SVG export from a design tool is that every single point and curve stays named and traceable in the source code. Anyone who later wants to adjust only the top edge changes exactly the affected curve to command, instead of searching through a whole machine generated path string trying to guess the right pair of numbers in it.


/* Organic blob shape from four curve segments */
.blob {
  clip-path: shape(
    from 20% 0%,
    curve to 100% 35% with 70% 0%,
    curve to 75% 100% with 100% 70%,
    curve to 0% 65% with 30% 100%,
    curve to 20% 0% with 0% 30%,
    close
  );
}

5. arc to: true circular arcs without a Bézier approximation

Alongside curve to for Bézier curves, shape() offers its own command, arc to, for true circular and elliptical arcs, specified through an end point, a radius, and a direction (cw for clockwise, ccw for counter clockwise). That is the decisive difference from a Bézier curve, which can only ever approximate a circle: an arc to arc is geometrically exact, which produces visibly smoother results than a roughly estimated curve for rounded corners or circular cutouts.

That makes it possible to build rounded rectangles with a precisely defined corner radius, entirely without border-radius, which is useful whenever the shape is part of a larger clip-path construct and border-radius alone is not enough because a straight edge also needs to be cut elsewhere.


/* Rounded rectangle via four arc-to commands */
.rounded-cutout {
  clip-path: shape(
    from 0 20px,
    arc to 20px 0 of 20px cw,
    line to calc(100% - 20px) 0,
    arc to 100% 20px of 20px cw,
    line to 100% calc(100% - 20px),
    arc to calc(100% - 20px) 100% of 20px cw,
    line to 20px 100%,
    arc to 0 calc(100% - 20px) of 20px cw,
    close
  );
}

6. Animating shape(): soft shape transitions without JavaScript

Because two shape() values with the same number and order of commands are interpolatable, the browser can animate cleanly between them, for example for a hover state that slightly distorts a blob shape, or a scroll driven transition from a round to an angular silhouette. The only requirement is that the start and end shapes match structurally, meaning the same number of curve to or arc to commands in the same order.

That is a clear advantage over animating an SVG path attribute, which can barely be interpolated meaningfully without an extra JavaScript library like GSAP, because the browser does not automatically map two arbitrary path strings onto each other. With shape(), a plain CSS transition declaration on clip-path is enough, and the browser handles the full interpolation of every individual coordinate.


.blob-interactive {
  clip-path: shape(from 20% 0%, curve to 100% 35% with 70% 0%,
    curve to 75% 100% with 100% 70%, curve to 0% 65% with 30% 100%,
    curve to 20% 0% with 0% 30%, close);
  transition: clip-path 0.4s ease;
}

.blob-interactive:hover {
  clip-path: shape(from 15% 0%, curve to 100% 25% with 80% 0%,
    curve to 80% 100% with 100% 80%, curve to 0% 75% with 20% 100%,
    curve to 15% 0% with 0% 20%, close);
}

7. Browser support and a fallback onto polygon()

shape() is newer than polygon(), and support in current browsers is growing but not yet as widespread as the established clip-path functions. The pragmatic approach is a two step fallback: declare a rough polygon() approximation first, which works in every browser, and right after it the finer shape() version, which overrides the previous declaration in browsers that support it.

An @supports query makes that safeguard explicit and prevents a completely angular or missing shape from showing up in older browsers. It matters here that the polygon() approximation is visually close enough to the target shape that the difference does not read as a break for users without shape() support, but at most as a slightly more angular variant of the same shape.


.blob {
  /* Fallback: rough polygon approximation for older browsers */
  clip-path: polygon(20% 0%, 65% 5%, 100% 35%, 90% 70%, 75% 100%, 35% 95%, 0% 65%, 5% 25%);
}

@supports (clip-path: shape(from 0 0, line to 100% 100%)) {
  .blob {
    clip-path: shape(from 20% 0%, curve to 100% 35% with 70% 0%,
      curve to 75% 100% with 100% 70%, curve to 0% 65% with 30% 100%,
      curve to 20% 0% with 0% 30%, close);
  }
}

8. A practical workflow: DevTools editor, design tools and maintainability

Current browser DevTools now offer a visual editor for clip-path values that also grabs and drags shape() paths directly in the canvas, similar to a vector tool in a design program. That speeds up fine tuning considerably, because control points can be adjusted with the mouse instead of repeatedly changing numeric values in the editor, while the result immediately lands as a readable CSS value in the stylesheet.

Compared to an SVG path exported from Figma or Illustrator, shape() also stays part of the CSS build process, with no extra asset file, no extra HTTP request, and no separate upkeep between design tool and repository. For a team that adjusts shapes frequently, for example on seasonal campaign pages, that noticeably reduces friction between design and implementation.

9. Practical use: campaign areas, badges and decorative dividers

In a shop context, shape() fits particularly well for seasonal campaign areas with an organic silhouette, for badge and sticker elements with soft instead of sharp corners, and for decorative section dividers that draw a gentle wave instead of a hard edge between two areas. In all three cases, a single shape() declaration replaces an extra SVG asset that would otherwise need separate hosting, loading, and re-exporting with every design adjustment.

It still matters to treat the shape as a purely decorative element here as well: interactive areas like buttons or links should never be made clickable exclusively through a shape() outline, without keeping the underlying click target large enough for keyboard and screen reader use.

Function Curves possible Readability Typical use
polygon() No, straight edges only High, simple coordinates Chevrons, arrows, diagonal sections
shape() with curve to Yes, Bézier curves High, named commands Organic blobs, soft edges
shape() with arc to Yes, true circular arcs High, named commands Precise rounding, circular cutouts
External SVG clipPath Yes, arbitrarily complex Low, generated path string Very complex, static illustrations

Mironsoft

Modern CSS, layout architecture and rendering performance

CSS that stays maintainable instead of breaking with every change?

We review existing stylesheets for specificity chaos and layout thrashing, then build a CSS architecture with cascade layers, custom properties and modern layout primitives that still makes sense after the tenth feature.

CSS Audit

Systematically uncovering specificity issues, cascade conflicts and unused selectors.

Architecture Refactoring

Introducing cascade layers, custom properties and design tokens cleanly.

Performance Tuning

Fixing layout thrashing, expensive selectors and rendering bottlenecks.

10. Summary

clip-path shape() in CSS: The Essentials at a Glance

Core idea

shape() brings real curve commands like curve to and arc to directly into clip-path, with no detour through an external SVG path string.

Vs. polygon()

polygon() stays the simpler choice for straight edges, shape() pays off once an edge is actually meant to look round or organic.

Animatable

Two structurally matching shape() values interpolate automatically in the browser, a plain CSS transition is enough for a soft shape change.

Fallback

A polygon() approximation declared before the shape() value plus an @supports query cleanly protect older browsers.

11. FAQ: clip-path shape() in CSS: The Essentials at a Glance

1What does the shape() function do in clip-path?
shape() extends clip-path with real curve commands like curve to, arc to and smooth to, written directly as readable CSS syntax instead of embedding an SVG path string.
2How does shape() differ from polygon()?
polygon() only knows straight lines between vertices. shape() can additionally draw true curves and circular arcs, making it fit for organic, round shapes that polygon() could only roughly approximate.
3Can I animate shape() values?
Yes, as long as the start and end shape use the same number and order of commands. A plain CSS transition on clip-path is then enough for a soft shape transition.
4What is the difference between curve to and arc to?
curve to draws a Bézier curve through one or two control points, arc to draws a geometrically exact circular or elliptical arc through an end point, radius and direction.
5Do I need a design tool like Figma for shape()?
No, shape() paths can be written directly in the stylesheet and adjusted with the mouse in the browser DevTools' visual clip-path editor, with no external graphics program needed.
6How do I safeguard shape() for older browsers?
Declare a rough polygon() approximation first, which works everywhere, then the finer shape() version, optionally additionally guarded with an @supports query.
7Does shape() completely replace the use of SVG?
No. For very complex, static illustrations an external SVG file still makes sense. shape() mainly replaces the cases where a simple curve or arc used to justify an entire SVG asset.
8Do calc() expressions work in shape() coordinates?
Yes, all coordinates accept normal CSS length units, percentages and calc() expressions, which makes it possible to describe shapes with a fixed border width independent of the element's size.
9Is a shape() shape performant to render?
Yes, the browser renders shape() like any other clip-path value through a path calculation, without the per pixel opacity calculation a mask requires. That is computationally cheaper than mask-image.
10Can an area shaped with shape() be interactive?
Yes, but the clickable area should stay large enough despite the visual outline, so keyboard and screen reader use as well as touch operation are not restricted.