CSS Shapes Module Level 2: Mastering the shape() Function for Fluid Paths

In a dark and futuristic laboratory, robotic
arms project cyan and violet lasers to sculpt a 3D geometric wave shape

Modern web design constantly seeks to break free from the rigidity of rectangular boxes. For a long time, integrating complex geometric shapes or organic curves required either using external SVG vector images or applying heavy mathematical calculations to polygons.

With the arrival of CSS Shapes Module Level 2, a new native feature changes the game: the shape() function. It allows you to draw complex and fluid paths directly inside your stylesheets. In this comprehensive guide, we will explore the technical inner workings of this function, its commands, and its benefits compared to older methods.

Why Were Older Methods Problematic?

Before the introduction of shape(), developers mainly relied on two tools to create custom shapes using the clip-path property:

  1. The polygon() function: Highly effective for geometric shapes based on straight lines (triangles, diamonds, stars). However, as soon as a curve was needed, you had to multiply the anchor points, making the code unreadable and difficult to maintain.
  2. The path() function: It allows you to use SVG path syntax (such as d="M 10,10 L 20,20 ..."). Its main drawback lies in its rigidity. The path() function only accepts absolute values in pixels. If your container changes size, the path remains frozen, which breaks the adaptive (responsive) behavior of the interface.

The shape() function solves this problem by combining the vector drawing power of SVG syntax with the flexibility of CSS measurement units (percentages, em, rem, or mathematical calculation functions).

What Is the shape() Function?

The shape() function is a CSS value mainly applied to the clip-path property (to clip an element) and the offset-path property (to animate an element along a precise path).

Writing it relies on a defined starting point, followed by a series of drawing commands very similar to those found in the SVG <path> element. The key difference is that each coordinate can use fluid expressions.

The Basic Structure of a shape() Path

To understand how to draw with this function, imagine holding a virtual pencil over a sheet of paper:

  • The starting point (from): You place your pencil on the paper at specific coordinates.
  • Movement commands (move, line, curve, arc): You move your pencil to draw straight segments, curves, or circular arcs.
  • The closing command (close): You draw one last straight line to connect your current position back to the initial starting point.

Drawing Commands Explained Simply

The table below summarizes the main commands available within the shape() function:

CommandRoleExample Usage
fromDefines the mandatory starting coordinates of the path.from 0% 0%
move to / move byMoves the pencil without drawing a line.move to 50px 50px
line to / line byDraws a straight line to a given point.line to 100% 50%
hline to / hline byShortcut to draw a purely horizontal line.hline to 80%
vline to / vline byShortcut to draw a purely vertical line.vline to 90%
curve toDraws a Bézier curve using control points.curve to 100% 100% with 50% 80%
arc toDraws a circular or elliptical arc.arc to 50% 50% of 10%
closeAutomatically closes the shape by returning to the start.close

Note on the difference between “to” and “by”: The to instruction uses absolute coordinates relative to the container’s origin (the top-left corner at 0,0). The by instruction uses coordinates relative to your virtual pencil’s current position.

A Tool to Draw Your Shapes Live

It is not always easy to visualize a geometric path just by looking at lines of code.

To help you, you can use this free interactive tool: shape.lionel-peramo.com.

This tool lets you draw your shapes visually on your screen. It then automatically generates the corresponding CSS code, ready to be copied into your stylesheet.

Practical Case 1: Creating a Responsive Speech Bubble

To illustrate the writing simplicity and fluidity of this new specification, let’s create an adaptive speech bubble. The shape must automatically adjust to the dimensions of the text it contains.

.speech-bubble {
  width            : clamp(15rem, 45vw, 35rem);
  height           : clamp(10rem, 30vh, 22rem);
  background-color : oklch(25% 0.01 250);
  color            : oklch(98% 0.005 250);
  clip-path        : shape(
    from 0% 0%,
    line to 100% 0%,
    line to 100% min(80%, 18rem),
    line to min(60%, 20rem) min(80%, 18rem),
    line to min(50%, 17rem) 100%,
    line to min(40%, 14rem) min(80%, 18rem),
    line to 0% min(80%, 18rem),
    close
  );
}

Breaking Down the Bubble’s Path

  1. from 0% 0%: The pencil starts in the top-left corner.
  2. line to 100% 0%: We draw a horizontal line to the top-right corner.
  3. line to 100% min(80%, 18rem): We go down along the right edge. The height of the bubble (excluding the arrow) will stop at 80% of the element’s total height, without exceeding 18rem.
  4. line to min(60%, 20rem) min(80%, 18rem): We start drawing the bottom arrow by moving toward the center.
  5. line to min(50%, 17rem) 100%: We go down to the lowest point of our arrow (at 100% of the container’s height).
  6. line to min(40%, 14rem) min(80%, 18rem): We head back up toward the body of the bubble.
  7. line to 0% min(80%, 18rem): We finish the lower horizontal line toward the left edge.
  8. close: The browser automatically draws the remaining vertical left segment to close the shape.

Thanks to the min() and clamp() mathematical functions, the structure adjusts dynamically without needing any media queries (@media) to reorganize the box’s geometry.

Practical Case 2: A Banner with a Curved Bottom Edge

Let’s look at how to introduce dynamic curves with the curve to command. We want to create a hero banner section with a smooth wave-like bottom edge.

.hero-banner:not(.is-minimal) {
  width            : 100%;
  height           : clamp(20rem, 50vh, 45rem);
  background-image : url('banner-image.avif');
  clip-path        : shape(
    from 0% 0%,
    line to 100% 0%,
    line to 100% min(90%, 40rem),
    curve to 0% min(90%, 40rem) with 50% 100%,
    close
  );
}

How Does the Curve Work Here?

The curve to 0% min(90%, 40rem) with 50% 100% command tells the browser:

  • To move the path from the current point (the bottom-right corner) to the bottom-left corner (0% min(90%, 40rem)).
  • To use a control point located at the very bottom center of the element (50% 100%). This control point acts like a virtual magnet pulling the path downward, creating a symmetrical curve.

Performance and SEO Benefits

Integrating the shape() function brings significant benefits to your technical architecture and web page visibility.

Optimal Hardware Performance

The clip-path property applied with native CSS functions like shape() is directly handled by the device’s graphics processor (GPU). Unlike alternative solutions using JavaScript to recalculate SVG tag positions when resizing the window, shape() runs almost instantaneously.

This optimization reduces calculation time on the browser’s main thread, measurably improving your Core Web Vitals performance metrics:

  • CLS (Cumulative Layout Shift): No unexpected content shifts while loading the page.
  • LCP (Largest Contentful Paint): The visual rendering of your largest graphical blocks displays faster.

A Indirect But Strong Impact on SEO

Search engines value user experience and loading speed. A website that responds quickly and stably on both mobile and desktop enjoys better indexing potential.

Additionally, by eliminating superfluous external SVG files or geometric adjustment JS scripts, you reduce the overall weight of your pages. This makes it easier for search engine crawlers to explore your site.

Do All Browsers Support This Function?

The shape() function works on all recent web browsers. It has been an official standard since 2026.

Here is the list of compatible versions:

  • Chrome and Edge: from version 135.
  • Safari: from version 18.4.
  • Firefox: from version 148.

How to Help Older Computers?

Some users have older computers. For these visitors, you must provide a backup solution. In computer science, we call this a fallback.

Here is how to do it in CSS:

  1. You provide a simple shape using the polygon() command. All browsers understand it.
  2. You use the @supports rule to ask the browser a question.
  3. If the browser understands the shape() function, it applies the fluid shape instead of the simple one.
.card-shape {
  clip-path : polygon(0% 0%, 100% 0%, 100% 80%, 60% 80%, 50% 100%, 40% 80%, 0% 80%);
}

@supports (clip-path: shape(from 0px 0px, line to 0px 0px)) {
  .card-shape {
    clip-path : shape(
      from 0% 0%,
      line to 100% 0%,
      line to 100% min(80%, 18rem),
      line to min(60%, 20rem) min(80%, 18rem),
      line to min(50%, 17rem) 100%,
      line to min(40%, 14rem) min(80%, 18rem),
      line to 0% min(80%, 18rem),
      close
    );
  }
}

Thanks to this code, all users can visit your site without any display issues.

Key Takeaways

The shape() function changes how we code websites.

Here are its 3 major benefits:

  • It is fluid: the shape adapts to the screen size without any extra code.
  • It is fast: the browser draws the shape directly. Your site displays faster.
  • It is modern: combined with the OKLCH color space, it helps you create cleaner and more accessible designs.

Try using the shape() function in your next project to build unique and lightweight interfaces.


Frequently Asked Questions (FAQ)

What is the main difference between path() and shape()? The path() command only uses pixels. The shape cannot change size. The shape() function uses percentages. The shape automatically adapts to your screen size.
Can you mix different measurement units in shape()? Yes. This is a major strength of the shape() function. You can mix percentages (like 50%), pixels (like 20px), and relative measurements (like 3rem) within the same drawing.
Can you move an object along a shape() path? Yes. You can draw an invisible line using the shape() function. An object on your page can then slide along this line to create an animation.
Can you use shape() to force text to wrap around a shape? Yes. You can use the shape() function in two ways: 1. To clip an element on your page using clip-path. 2. To force text to wrap around the element using shape-outside.
Lionel Péramo
Lionel Péramo
Web Performance & Eco-design Expert

Full Stack Developer and creator of the OTRA framework (PHP) and EcoComposer library. I write to make the web faster and more inclusive.

About me →