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:
- Start at the upper-left corner.
- Draw a line to the upper-right corner.
- Draw a line to the lower-right corner.
- Draw a line to the lower-left corner.
- 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);
}
}
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;
}
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;
}
}
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
nonzeroandevenodd. - Commands such as
line,curve, andarcrequire valid coordinate values. - The
tokeyword uses positions relative to the reference box. - The
bykeyword 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-outsideproperty.
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().
