CSS shape() Function

The CSS shape() function defines a custom shape or path using a starting point and a sequence of drawing commands.

It is used with properties such as clip-path and offset-path to create custom clipping regions and motion paths. Unlike path(), it supports percentages, CSS length units, custom properties, and math functions, making shapes easier to adapt to different element sizes.

Syntax

shape([fill-rule] from starting-point, shape-command, ...);

The shape() function begins with an optional fill rule, followed by a required starting point and one or more comma-separated drawing commands.

For example:

clip-path: shape(
  from 50% 0,
  line to 100% 100%,
  line to 0 100%,
  close
);

This creates a triangular clipping region.

Another example:

clip-path: shape(
  from 0 0,
  line to 100% 0,
  line to 100% 80%,
  curve to 0 80% with 50% 100%,
  close
);

This creates a shape with a curved lower edge.

The function can also define a motion path:

offset-path: shape(
  from 0 50%,
  curve to 100% 50% with 50% 0
);

The function returns a CSS <basic-shape> value.

Parameters

The shape() function contains three main components.

Component Description Required
fill-rule Determines how overlapping or intersecting regions are filled. Accepts nonzero or evenodd. No
from starting-point Specifies the initial position of the path. Yes
shape-command Defines a line, curve, arc, movement, or path closure. At least one command is required. Yes

The default fill rule is nonzero.

For example:

clip-path: shape(
  evenodd from 0 0,
  line to 100% 0,
  line to 100% 100%,
  line to 0 100%,
  close
);

This explicitly selects the evenodd fill rule.

Each drawing command is separated from the next by a comma.

Unlike SVG path strings, the commands are written directly using CSS keywords and values.

How It Works

The shape() function constructs a path by processing its commands in sequence.

Each command begins at the endpoint of the preceding command unless a move command establishes a new starting point.

Consider the following:

clip-path: shape(
  from 0 0,
  line to 100% 0,
  line to 100% 100%,
  line to 0 100%,
  close
);

The browser processes the shape in the following order:

  1. Start at the upper-left corner.
  2. Draw a line to the upper-right corner.
  3. Draw a line to the lower-right corner.
  4. Draw a line to the lower-left corner.
  5. Close the path by connecting the final point to the starting point.

The resulting clipping region is a rectangle.

Changing the coordinates or replacing straight lines with curves produces different shapes.

The function does not create a new HTML element. It defines geometry that another CSS property uses.

When used with clip-path, the geometry determines which parts of an element remain visible. When used with offset-path, it determines the route an element follows.

Shape Commands

The shape() function provides several commands for constructing paths.

Command Description
move Moves to another point without drawing a connecting line.
line Draws a straight line to a specified point.
hline Draws a horizontal line.
vline Draws a vertical line.
curve Draws a quadratic or cubic Bézier curve.
smooth Draws a smooth continuation of a preceding curve.
arc Draws part of an ellipse between two points.
close Closes the current subpath by connecting its endpoint to its starting point.

The move Command

shape(
  from 0 0,
  move to 50% 50%,
  line to 100% 100%
)

The move command changes the current position without drawing a line from the previous point.

The line Command

shape(
  from 0 0,
  line to 100% 100%
)

This draws a straight line between two points.

Horizontal and Vertical Lines

shape(
  from 0 0,
  hline to 100%,
  vline to 100%,
  hline to 0,
  close
)

The hline and vline commands are convenient when only one coordinate needs to change.

The close Command

shape(
  from 50% 0,
  line to 100% 100%,
  line to 0 100%,
  close
)

The close command draws a closing segment back to the current subpath's starting point.

A closed path is especially useful when defining a clipping region.

Coordinates and Units

The shape() function supports CSS coordinates expressed using lengths, percentages, and supported CSS math expressions.

Absolute Coordinates With to

The to keyword specifies a destination relative to the reference box.

shape(
  from 0 0,
  line to 100px 50px
)

This draws a line to the point 100px horizontally and 50px vertically from the reference box's origin.

Relative Coordinates With by

The by keyword specifies a displacement from the current point.

shape(
  from 10px 10px,
  line by 80px 40px
)

This draws a line 80px to the right and 40px downward from the starting point.

Percentage Coordinates

shape(
  from 50% 0,
  line to 100% 100%,
  line to 0 100%,
  close
)

Percentage coordinates adapt to the dimensions of the reference box.

Horizontal percentages use the reference box's width, while vertical percentages use its height.

CSS Math Functions

shape(
  from 0 0,
  line to calc(100% - 20px) 0,
  line to 100% 100%,
  line to 0 100%,
  close
)

The calc() function allows coordinates to combine different CSS units.

CSS Custom Properties

.custom-shape {
  --cut: 20px;
  clip-path: shape(
    from 0 0,
    line to calc(100% - var(--cut)) 0,
    line to 100% var(--cut),
    line to 100% 100%,
    line to 0 100%,
    close
  );
}

Changing the --cut custom property adjusts the clipped corner without rewriting the entire shape.

Fill Rules

The optional fill rule determines how the interior of a shape is calculated when paths overlap or intersect.

Value Description
nonzero Uses the nonzero winding rule to determine which regions are filled. This is the default.
evenodd Uses the even-odd rule, which can create unfilled regions inside overlapping paths.

For example:

clip-path: shape(
  evenodd from 0 0,
  line to 100% 0,
  line to 100% 100%,
  line to 0 100%,
  close,
  move to 25% 25%,
  line to 75% 25%,
  line to 75% 75%,
  line to 25% 75%,
  close
);

The outer path defines a large rectangle, while the inner path defines a smaller rectangle.

With the evenodd fill rule, the inner region becomes a hole in the clipping area.

For simple shapes without overlapping regions, the default nonzero rule is usually sufficient.

Curves and Arcs

The shape() function supports curved paths in addition to straight lines.

Quadratic Bézier Curves

A quadratic Bézier curve uses one control point.

clip-path: shape(
  from 0 0,
  line to 100% 0,
  line to 100% 70%,
  curve to 0 70% with -50% 60% from start,
  close
);

The control point determines how the curve bends between its starting and ending positions.

Cubic Bézier Curves

A cubic Bézier curve uses two control points separated by a slash.

offset-path: shape(
  from 0 50%,
  curve to 100% 50% with 25% -50% from start / -25% 50% from end
);

The two control points provide more control over the direction and curvature of the path.

Smooth Curves

The smooth command continues a previous curve using a reflected control point.

offset-path: shape(
  from 0 50%,
  curve to 50% 50% with 25% -40% from start,
  smooth to 100% 50%
);

This produces a smooth transition between connected curve segments.

Elliptical Arcs

The arc command draws part of an ellipse.

offset-path: shape(
  from 0 50%,
  arc to 100% 50% of 50% 50% cw
);

The of keyword introduces the ellipse radii.

Additional keywords control the direction and size of the arc.

Keyword Description
cw Uses a clockwise arc direction.
ccw Uses a counterclockwise arc direction.
large Selects the larger available arc.
small Selects the smaller available arc.
rotate Specifies the ellipse's rotation angle.

Curves and arcs are useful for creating rounded clipping regions, decorative shapes, and non-linear motion paths.

Using shape() With clip-path

The clip-path property defines which parts of an element are visible.

When combined with shape(), it can create custom clipping regions.

Triangle

.triangle {
  clip-path: shape(
    from 50% 0,
    line to 100% 100%,
    line to 0 100%,
    close
  );
}

This creates a triangle with its point at the top.

Clipped Corner

.clipped-corner {
  clip-path: shape(
    from 0 0,
    line to 80% 0,
    line to 100% 20%,
    line to 100% 100%,
    line to 0 100%,
    close
  );
}

This creates a rectangle with its upper-right corner cut away.

Curved Bottom Edge

.curved-panel {
  clip-path: shape(
    from 0 0,
    line to 100% 0,
    line to 100% 70%,
    curve to 0 70% with -50% 60% from start,
    close
  );
}

This creates a panel with a curved lower boundary.

The clipped portions are not painted, but the element retains its original layout dimensions.

Clipping can also affect hit testing, so avoid hiding essential interactive content behind a clipping path.

Using shape() With offset-path

The offset-path property defines a path along which an element can be positioned or animated.

When used with shape(), the motion path can contain lines, curves, and arcs.

For example:

.moving-box {
  offset-path: shape(
    from 0 50%,
    curve to 100% 50% with 50% 0
  );
  offset-distance: 50%;
}

The offset-distance property specifies how far along the path the element is positioned.

To animate the element:

@keyframes follow-path {
  from {
    offset-distance: 0%;
  }
  to {
    offset-distance: 100%;
  }
}
.moving-box {
  animation: follow-path 4s linear infinite;
}

The element follows the path as offset-distance changes.

The offset-rotate property can control whether the element rotates to follow the path's direction.

.moving-box {
  offset-rotate: auto;
}

For an element that should maintain its original orientation, use:

.moving-box {
  offset-rotate: 0deg;
}

Unlike clipping, motion paths change an element's rendered position without changing its original space in document layout.

shape() vs. path()

The shape() and path() functions can both define custom paths, but their syntax and flexibility differ.

Feature shape() path()
Syntax CSS drawing commands SVG path data string
Percentages Supported Not supported inside the path string
CSS length units Supported Path coordinates use SVG user units
CSS math functions Supported Not supported inside the path string
CSS custom properties Can be used within coordinate values Cannot be inserted into individual coordinates within the string
Responsive scaling Can adapt using percentages Coordinates are fixed within the path data

For example, a triangle using path():

clip-path: path("M 100 0 L 200 200 L 0 200 Z");

The equivalent triangle using shape():

clip-path: shape(
  from 50% 0,
  line to 100% 100%,
  line to 0 100%,
  close
);

The shape() version can adapt to changes in the reference box dimensions because it uses percentages.

The path() version uses fixed coordinates and does not automatically resize its path data to match a changing element.

Example

The following example compares three shapes created with shape().

HTML:

<div class="shape-demo">
  <div class="shape-item">
    <div class="shape-box shape-triangle"></div>
    <p>Triangle</p>
  </div>
  <div class="shape-item">
    <div class="shape-box shape-cut"></div>
    <p>Clipped Corner</p>
  </div>
  <div class="shape-item">
    <div class="shape-box shape-chevron"></div>
    <p>Chevron</p>
  </div>
</div>

CSS:

.shape-demo {
  display: flex;
  flex-wrap: wrap;
  gap: 25px;
  padding: 20px 0;
}
.shape-item {
  width: 140px;
  text-align: center;
}
.shape-box {
  width: 140px;
  height: 120px;
  background-color: #3498db;
}
.shape-triangle {
  clip-path: polygon(50% 0, 100% 100%, 0 100%);
}
.shape-cut {
  clip-path: polygon(0 0, 80% 0, 100% 20%, 100% 100%, 0 100%);
}
.shape-chevron {
  clip-path: polygon(0 0, 70% 0, 100% 50%, 70% 100%, 0 100%, 30% 50%);
}
@supports (clip-path: shape(from 0 0, line to 100% 0, close)) {
  .shape-triangle {
    clip-path: shape(from 50% 0, line to 100% 100%, line to 0 100%, close);
  }
  .shape-cut {
    clip-path: shape(from 0 0, line to 80% 0, line to 100% 20%, line to 100% 100%, line to 0 100%, close);
  }
  .shape-chevron {
    clip-path: shape(from 0 0, line to 70% 0, line to 100% 50%, line to 70% 100%, line to 0 100%, line to 30% 50%, close);
  }
}
Browser Result

Triangle

Clipped Corner

Chevron

The three shapes use different sequences of straight-line commands.

The polygon() declarations provide fallbacks for browsers that do not support shape().

In supporting browsers, the shape() declarations replace the fallback clipping paths.

Curved Shape Example

The following example uses a Bézier curve to create a panel with a curved lower edge.

HTML:

<div class="curve-panel">
  <h3>Curved Panel</h3>
  <p>A custom shape created with CSS.</p>
</div>

CSS:

.curve-panel {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  width: 100%;
  max-width: 450px;
  height: 200px;
  padding: 20px;
  color: #fff;
  background-color: #3498db;
  clip-path: polygon(0 0, 100% 0, 100% 80%, 50% 100%, 0 80%);
}
@supports (clip-path: shape(from 0 0, line to 100% 0, close)) {
  .curve-panel {
    clip-path: shape(
      from 0 0,
      line to 100% 0,
      line to 100% 70%,
      curve to 0 70% with -50% 60% from start,
      close
    );
  }
}
.curve-panel h3,
.curve-panel p {
  margin: 0 0 10px;
  color: #fff;
}
Browser Result

Curved Panel

A custom shape created with CSS.

The curved boundary is defined by the curve command and its control point.

The percentage-based coordinates allow the curve to adapt to the width of the panel.

In browsers without shape() support, the panel displays a simpler polygon-based lower edge.

Motion Path Example

The following example demonstrates an element following a custom path.

HTML:

<div class="motion-scene">
  <div class="motion-object"></div>
</div>

CSS:

.motion-scene {
  position: relative;
  width: 100%;
  max-width: 400px;
  height: 180px;
  overflow: hidden;
  background-color: #f1f1f1;
}
.motion-object {
  position: absolute;
  top: 0;
  left: 0;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  background-color: #3498db;
}
@keyframes shape-motion {
  from {
    offset-distance: 0%;
  }
  to {
    offset-distance: 100%;
  }
}
@supports (offset-path: shape(from 0 0, line to 100px 0)) {
  .motion-object {
    offset-path: shape(
      from 30px 100px,
      curve to 350px 100px with 160px -60px from start
    );
    offset-rotate: 0deg;
    animation: shape-motion 4s linear infinite alternate;
  }
}
@media (prefers-reduced-motion: reduce) {
  .motion-object {
    animation: none;
  }
}
Browser Result

The circle follows the curved motion path and reverses direction at the end of each animation cycle.

The offset-distance property controls its position along the path.

Browsers that do not support shape() display the circle without the motion animation.

Responsive Shapes

One of the main advantages of shape() is its ability to use percentage-based coordinates.

For example:

.responsive-shape {
  width: 100%;
  max-width: 500px;
  height: 200px;
  background-color: #3498db;
  clip-path: shape(
    from 50% 0,
    line to 100% 100%,
    line to 0 100%,
    close
  );
}

The triangular shape adapts as the element's width changes.

Custom properties can also control individual coordinates:

.responsive-shape {
  --point: 50%;
  clip-path: shape(
    from var(--point) 0,
    line to 100% 100%,
    line to 0 100%,
    close
  );
}

Changing --point moves the top point of the triangle.

For example:

@media (max-width: 600px) {
  .responsive-shape {
    --point: 30%;
  }
}

On smaller screens, the top point shifts toward the left.

This approach allows custom shapes to respond to layout changes without requiring separate SVG path strings for each viewport size.

Transitions and Animations

The shape() function can be used in CSS transitions and animations when the path definitions are compatible for interpolation.

For smooth interpolation, the shapes generally need matching command sequences.

Shape Transition

.morphing-shape {
  width: 150px;
  height: 150px;
  background-color: #3498db;
  clip-path: shape(
    from 50% 0,
    line to 100% 100%,
    line to 0 100%,
    close
  );
  transition: clip-path 0.5s ease;
}
.morphing-shape:hover {
  clip-path: shape(
    from 50% 0,
    line to 90% 90%,
    line to 10% 90%,
    close
  );
}

Both shapes use the same sequence of commands, allowing their coordinates to interpolate smoothly in supporting browsers.

Keyframe Animation

@keyframes shape-morph {
  from {
    clip-path: shape(
      from 50% 0,
      line to 100% 100%,
      line to 0 100%,
      close
    );
  }
  to {
    clip-path: shape(
      from 50% 10%,
      line to 90% 90%,
      line to 10% 90%,
      close
    );
  }
}
.animated-shape {
  animation: shape-morph 2s ease-in-out infinite alternate;
}

The animation changes the shape by interpolating its coordinates.

If the command sequences are incompatible, the browser may use a discrete change rather than a smooth transition.

Reduced Motion

@media (prefers-reduced-motion: reduce) {
  .animated-shape {
    animation: none;
  }
  .morphing-shape {
    transition: none;
  }
}

This removes the continuous animation and disables the transition for users who request reduced motion.

Common Uses

The shape() function is useful for creating custom geometry directly in CSS.

  • Custom image clipping: Display images inside irregular or decorative shapes.
  • Decorative panels: Create angled corners, curved edges, and other nonrectangular designs.
  • Responsive shapes: Use percentage coordinates that adapt to changing element dimensions.
  • Motion paths: Move elements along lines, curves, and arcs.
  • Shape transitions: Animate compatible paths between different outlines.
  • Interface decorations: Create chevrons, banners, badges, and geometric accents.
  • Reusable shapes: Adjust path coordinates using CSS custom properties.
  • Complex clipping: Use multiple subpaths and fill rules to create cutouts.

For example, a decorative banner can use a clipped corner:

.banner {
  --corner: 30px;
  background-color: #3498db;
  clip-path: shape(
    from 0 0,
    line to calc(100% - var(--corner)) 0,
    line to 100% var(--corner),
    line to 100% 100%,
    line to 0 100%,
    close
  );
}

The --corner variable controls the size of the cutout.

Use shape() when a design needs more flexibility than the simpler circle(), ellipse(), or polygon() functions provide.

Limitations

The shape() function has several requirements and behaviors to consider.

  • The function requires a starting point introduced by from.
  • At least one shape command must follow the starting point.
  • Shape commands must be separated by commas.
  • The optional fill rule must appear before from.
  • The supported fill rules are nonzero and evenodd.
  • Commands such as line, curve, and arc require valid coordinate values.
  • The to keyword uses positions relative to the reference box.
  • The by keyword uses displacements relative to the current point.
  • Percentage coordinates depend on the reference box dimensions.
  • Clipping changes an element's visible region but not its original layout dimensions.
  • Motion paths change rendered positioning without changing the original layout space.
  • Smooth shape interpolation requires compatible path command sequences.
  • Browser support is newer than support for several established basic-shape functions.
  • The function is not a valid value for the shape-outside property.

The following is valid:

clip-path: shape(
  from 0 0,
  line to 100% 0,
  line to 100% 100%,
  close
);

The following is invalid because it omits the required starting point:

clip-path: shape(
  line to 100% 0,
  line to 100% 100%,
  close
);

The following is invalid because the drawing commands are not separated by commas:

clip-path: shape(
  from 0 0
  line to 100% 0
  line to 100% 100%
  close
);

Feature Detection

Use @supports to provide a fallback when shape() is unavailable.

.box {
  clip-path: polygon(50% 0, 100% 100%, 0 100%);
}
@supports (clip-path: shape(from 0 0, line to 100% 0, close)) {
  .box {
    clip-path: shape(
      from 50% 0,
      line to 100% 100%,
      line to 0 100%,
      close
    );
  }
}

The polygon() fallback works in browsers that do not support the newer function.

Feature detection checks the supported syntax but does not guarantee that every newer shape command or interpolation feature is implemented.

Browser Support

The CSS shape() function has Baseline Newly available browser support as of February 2026. It works across the latest major browser versions, but older browsers may not support it.

Because support is relatively recent, provide a fallback when the shape is important to the design. Established functions such as polygon() can often provide a simpler alternative.

Some advanced syntax and animation behaviors may have different implementation histories.

For current compatibility information, visit Can I Use? and search for shape().

Specifications

The shape() function is defined in the CSS Shapes Module Level 1 specification.

The specification defines its drawing commands, coordinate handling, fill rules, and interpolation behavior.

Related functions include path(), polygon(), circle(), and ellipse().