A Modern Approach to CSS Shapes

Searching for “how to create [shape] with CSS” is practically a rite of passage for front-end developers. The results are endless, and it’s tempting to copy the first snippet you find, drop it into your stylesheet, and move on. The problem? Many of those snippets are outdated, rely on magic numbers, and are hard to modify when your design inevitably changes.

This guide covers the most common CSS shapes using clean, modern techniques. The focus is not on memorizing a specific shape’s code, but on understanding the reusable tricks that let you build any shape you need with minimal, flexible markup.

CSS or SVG?

It’s a fair question: why not just use SVG for shapes? The honest answer is that you should use SVG if that’s the better tool for your project. SVG is a perfectly valid approach with its own syntax and considerations.

CSS, however, is often the better choice for decorative elements or when you need to style a specific HTML element that already contains real content. There’s no universal winner here. Your project’s requirements should guide whether a CSS shape fits the bill or whether SVG is the more appropriate solution.

A Handy Reference

Before diving into the code, it’s worth spending a few minutes exploring the CSS Shape website. It’s a growing collection of CSS-only shapes that I maintain regularly. Each shape’s CSS is optimized to be as flexible and efficient as possible. In most cases, the styles target a single HTML element, so you don’t have to clutter your markup. CSS variables are used liberally throughout, making it easy to tweak a shape to fit your needs without rewriting the underlying logic.

If you don’t have time to learn every technique by heart, that resource is a practical, ready-to-use reference you can bookmark for future projects.

Clipping Shapes With clip-path

The clip-path property with its polygon() function is the standard tool for CSS shapes. By working through several classic shapes, a few useful tricks emerge that make broader shape creation easier to handle.

Hexagons Without Full Coordinates

Creating a hexagon by listing all six of its points is straightforward: define dimensions and provide coordinates for each point.

.hexagon {
  width: 200px;
  aspect-ratio: 0.866; 
  clip-path: polygon(
    0% 25%,
    0% 75%,
    50% 100%, 
    100% 75%, 
    100% 25%, 
    50% 0%);
}

See the Pen [Hexagon shape using clip-path](https://codepen.io/t_afif/pen/JjVJJbG) by Temani Afif.

See the Pen Hexagon shape using clip-path by Temani Afif.

But there is a simpler route using just four points. The polygon() function accepts coordinates outside the element’s range — beyond [0% 100%]. You can clip outside the element’s boundaries, which cuts down the points needed.

Comparing a hexagon with six points versus a hexagon clipped with four points.
Figure 1: Clipping a hexagon with four points. (Large preview)

This draws a diamond shape where two points extend well past the intended boundaries. The lesson: allow yourself to think beyond the shape’s perimeter. The resulting CSS is noticeably compact:

.hexagon {
  width: 200px;
  aspect-ratio: cos(30deg); 
  clip-path: polygon(
    -50% 50%,
    50% 100%,
    150% 50%,
    50% 0
  );
}

Note the replacement of the magic number 0.866 with the trigonometric function cos(). The aspect ratio equals cos(30deg) in this construction, which is easier to recall than a hardcoded decimal.

Swapping the X and Y coordinate values produces a variation of the hexagon — a rotation that changes its orientation.

clip-path: polygon(X1 Y1, X2 Y2, ..., Xn Yn)
clip-path: polygon(Y1 X1, Y2 X2, ..., Yn Xn)

The result is a different arrangement of the same fundamental shape:

See the Pen [Another variation of the hexagon shape](https://codepen.io/t_afif/pen/BaEZrrP) by Temani Afif.

See the Pen Another variation of the hexagon shape by Temani Afif.

The aspect ratio flips to 1/cos(30deg) when axes are swapped. Since the coordinates change places, the ratio must invert to maintain the correct proportions.

Because this is a single style rule on one selector, it applies equally to an <img> element as a <div>:

See the Pen [CSS-only hexagon shapes (the modern way)](https://codepen.io/t_afif/pen/KKEMjxV) by Temani Afif.

See the Pen CSS-only hexagon shapes (the modern way) by Temani Afif.

Two practical lessons emerge here:

  • The polygon() function tolerates points outside the [0% 100%] range. This allows fewer clipping points and creates possibilities for additional shapes.
  • Swapping axes generates shape variations. In the hexagon above, trading X and Y values changes the hexagon’s direction.

Octagons and Repeated Values

An octagon has eight sides — think of a traffic stop sign. Applying the same trick from the hexagon, those eight sides can be established with only four points by using coordinates that fall outside the element’s bounds.

Comparing an octagon with eight points versus an octagon clipped with four points.
Figure 2: Clipping an octagon with four points. (Large preview)

While visualizing outside points takes some adjustment, the CSS looks familiar:

.octagon {
  width: 200px;  
  aspect-ratio: 1;  
  --o: calc(50% * tan(-22.5deg));
  clip-path: polygon(
    var(--o) 50%,
    50% var(--o),
    calc(100% - var(--o)) 50%,
    50% calc(100% - var(--o))
  );
}

Aside from a small trigonometric formula, the structure matches the hexagon: set dimensions, clip the points. The calculation is stored as a CSS variable to avoid repetition. For those who prefer not to derive such formulas themselves, references exist — an online collection of CSS shapes serves as a good starting point.

This shape applies to images just as easily:

See the Pen [CSS-only octagon shapes (the modern way)](https://codepen.io/t_afif/pen/LYaxqEg) by Temani Afif.

See the Pen CSS-only octagon shapes (the modern way) by Temani Afif.

There is still room to simplify the code further:

.octa {
  --w: 200px;

width: var(--w);\
aspect-ratio: 1;
margin: calc(var(--w) * tan(22.5deg) / 2);
clip-path: polygon(0 50%, 50% 0, 100% 50%, 50% 100%) margin-box;
} 

See the Pen [Octagon shape with margin-box](https://codepen.io/t_afif/pen/ZEZrLmr) by Temani Afif.

See the Pen Octagon shape with margin-box by Temani Afif.

The variable holding the math value (--o) is gone, replaced by --w for dimensions. A broader change: the margin property is set, and clip-path references the margin-box keyword. Instead of the default border-box reference, the clipping is now relative to the margin box.

Looking at the octagon’s four points, they sit outside the shape’s boundaries at an equal distance. Rather than incorporating that distance into each coordinate, declaring it on margin makes coordinate values much easier to derive.

From the original:

.octagon {
  --o: calc(50% * tan(-22.5deg));

clip-path: polygon(var(--o) 50%, 50% var(--o), calc(100% - var(--o)) 50%, 50% calc(100% - var(--o)));
} 

The revised version has a clearer clip-path, with simplicity traded for an extra property:

.octagon {
  --w: 200px;

margin: calc(var(--w) * tan(22.5deg) / 2);
clip-path: polygon(0 50%, 50% 0, 100% 50%, 50% 100%) margin-box;
} 

All --o variables vanish from clip-path, and the margin absorbs that value. A new variable --w defines the element’s dimensions because percentage values no longer work directly. This results in some margin around the element, but the calculation becomes simpler. To avoid the extra margin, use padding paired with negative margin of the same amount — another technique for keeping polygon() simple with images:

See the Pen [Different shapes using the same polygon](https://codepen.io/t_afif/pen/oNOOWqz) by Temani Afif.

See the Pen Different shapes using the same polygon by Temani Afif.

Five Points for a Star

Stars are tricky with clip-path because the coordinates need precision. A typical approach uses ten points, one for each vertex of the star’s outline.

But five points suffice, provided that the order of coordinates inside polygon() is handled deliberately. If you were drawing a star on paper without lifting the pen, you would trace the outline in a particular sequence:

Diagram of a star with 10 clip points next to a star with five clip points.
Figure 3: Drawing a star shape with five points instead of 10 points. (Large preview)
Gold star with five points that are labeled one through five, starting at the top point.
Figure 4: Drawing a star with a single line illustrates the order of the points we need to create the shape. (Large preview)

The same continuous-line logic transfers cleanly to CSS. The lines inside the clipping path can intersect — the classic star pattern relies on it. That insight often gets overlooked when building shapes by coordinates:

.star {
  width: 200px;  
  aspect-ratio: 1;
  clip-path: polygon(50% 0, /* (1) */
    calc(50%*(1 + sin(.4turn))) calc(50%*(1 - cos(.4turn))), /* (2) */
    calc(50%*(1 - sin(.2turn))) calc(50%*(1 - cos(.2turn))), /* (3) */
    calc(50%*(1 + sin(.2turn))) calc(50%*(1 - cos(.2turn))), /* (4) */
    calc(50%*(1 - sin(.4turn))) calc(50%*(1 - cos(.4turn)))  /* (5) */
   ); 
}

See the Pen [Star shape using clip-path](https://codepen.io/t_afif/pen/NWmvBeL) by Temani Afif.

See the Pen Star shape using clip-path by Temani Afif.

The trigonometric calculations keep accuracy high without magic numbers, the five-point implementation beats the ten-point equivalent:

.star {
  width: 200px;  
  aspect-ratio: 1;
  clip-path: polygon(50% 0, 79% 90%, 2% 35%, 98% 35%, 21% 90%); 
}

Looking at the coordinates, the star’s symmetry matters. The second and fifth points share Y coordinates; so do the third and fourth. The X values sit equidistant from the center: 79% - 50% = 50% - 21% equals 100% when summed.

This yields another lesson: look for symmetry to spot duplicated values. With patterns that mirror themselves, only three points truly need to be remembered or derived; the remaining ones come from the symmetry.

50% 0   /* (1) */
79% 90% /* (2)  --> (100% - 79%) = 21% 90% /* (5) */
 2% 35% /* (3)  --> (100% -  2%) = 98% 35% /* (4) */

Re-examining the hexagon and octagon with this lens also reveals repeated values that make the clip-path rules easier to memorize.

Polygons, Starbursts, and Slanted Rectangles

For cases where the number of points is unknown or configurable, shapes such as regular polygons and starbursts provide flexible generics. Starbursts are simply polygons where half of the interface points are moved inward toward the center:

A row of three multi-point star shapes above a row of three geometric shapes with different numbers of sides.
Geometric shapes come with a number of points and sides. (Large preview)
Animated illustration of a star morphing into a polygon.
Figure 6.

The coordinates can get tedious by hand, and online generators offer a better path for these dynamic shapes:

Still, understanding how the coordinates get calculated is worthwhile — an article on computing polygon and starburst coordinates covers those details.

For rectangles with one or two angled sides — parallelograms, trapezoids, skewed rectangles — the same CSS technique applies across all of them.

Showing two parallelograms and a trapezoid.
Figure 7: Parallelograms and a trapezoid. (Large preview)

Start with the basic rectangle described by its four corner points:

clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%)

This code renders nothing new because the element is already a rectangle, and the values are just 0 and 100%.

Then offset some values until the shape changes as wanted. With a 10px offset, a 0 becomes 10px, and a 100% becomes calc(100% - 10px).

Which value needs updating and when?

That answer is best found experimentally. Opening the browser’s developer tools and adjusting values live shows exactly how each coordinate moves the shape. Even experienced CSS developers rarely write complete shapes from memory without verifying visually. Starting from the basic rectangle and adding or updating points until arriving at the desired shape is common practice. For reference shapes, an online collection contains the full working code along with additional shapes to try.

Cutting Shapes With CSS Masks

Where clip-path handles polygon-based shapes, the mask property opens up circular and curvy possibilities that require gradients rather than coordinate plotting. The core concept is simple: gradients define which parts of an element remain visible. Everything transparent in the mask layer gets cut away.

Creating Holes and Circular Cuts

A radial-gradient makes quick work of punching a circular hole through an element:

mask: radial-gradient(50px, #0000 98%, #000);

Why reach for mask over a plain background? Masks offer more flexibility — any color works, and the technique applies to elements like <img> just as easily:

See the Pen [Hole shape](https://codepen.io/t_afif/pen/OJGgGve) by Temani Afif.

See the Pen Hole shape by Temani Afif.

Here is the first important lesson: the colors themselves don't matter when working with mask. Only the alpha channel counts. Opaque colors (commonly #000) keep elements visible; fully transparent colors (like #0000) cut them out.

Use hard color stops to achieve crisp edges, though leaving a tiny transition — say, stopping at 98% instead of 100% — helps avoid jagged rendering.

Shifting gradient positions produces different cuts. A circle sliced from an element's edge:

See the Pen [Circular cut from the top & bottom](https://codepen.io/t_afif/pen/MWRvBOL) by Temani Afif.

See the Pen Circular cut from the top & bottom by Temani Afif.

The same principle extends to cutting from both top and bottom:

See the Pen [Circular Cut at top and bottom](https://codepen.io/t_afif/pen/WNWEKdy) by Temani Afif.

See the Pen Circular Cut at top and bottom by Temani Afif.

Give the radial-gradient an explicit size so it repeats, and the cut becomes a decorative scooped border:

See the Pen [Scooped edges from top and bottom](https://codepen.io/t_afif/pen/eYoEjVa) by Temani Afif.

See the Pen Scooped edges from top and bottom by Temani Afif.

That last example shows how a few configuration tweaks pivot from a simple hole to an ornamental edge. Fancy borders of all kinds — wavy, spiked, scalloped — follow this same mask-and-gradient recipe. Two useful references for expanding on the technique:

That second article also shows the technique applied to decorative background patterns.

See the Pen [CSS only pattern](https://codepen.io/t_afif/pen/vYddpzK) by Temani Afif.

See the Pen CSS only pattern by Temani Afif.

Building Rounded Arcs With Composition

Gradients remain the tool of choice for rounded arc shapes — the kind frequently seen in loading spinners:

Circular progress element with rounded edges and gradient coloration
Figure 9: Circular progress element with rounded edges and gradient coloration. (Large preview)

This example introduces composition, an operation between two or more gradient mask layers. Composition can be defined with mask-composite or declared directly on the mask property. The diagram below shows how the layers stack:

Showing the steps that go from a full circle to an unclosed circle with rounded edges.
Figure 10: Combining radial and conical gradients to establish the final shape. (Large preview)

The pattern: a radial-gradient makes a full circle, a conic-gradient adds another shape below it, and an intersect composition between them leaves an unclosed circle. Two extra radial gradients add the rounded endpoints using the default add composition.

Gradients are familiar territory from styling background, but composition is the new trick worth keeping in mind — it unlocks a wide range of forms.

.arc {
  --b: 40px; /* border thickness */
  --a: 240deg; /* progression */  

--_g:/var(--b) var(--b) radial-gradient(50% 50%,#000 98%,#0000) no-repeat;
mask:
top var(--_g),
calc(50% + 50% * sin(var(--a)))
calc(50% - 50% * cos(var(--a))) var(--_g),
conic-gradient(#000 var(--a), #0000 0) intersect,
radial-gradient(50% 50%, #0000 calc(100% - var(--b)), #000 0 98%, #0000)
} 

See the Pen [Progress circle using mask](https://codepen.io/t_afif/pen/eYoEpom) by Temani Afif.

See the Pen Progress circle using mask by Temani Afif.

Even when the syntax looks intimidating, CSS variables make adjustments straightforward. Most shapes benefit from configuring a few variables that drive the underlying math. Don't get lost in the formulas; focus on how gradients and composition combine to achieve the effect.

Notably, the same visual result can be reached with entirely different gradient setups:

.arc {
  --b: 40px; /* border thickness */
  --a: 250deg; /* progression */

padding: var(--b);
border-radius: 50%;

--_g: /var(--b) var(--b) radial-gradient(50% 50%, #000 97%, #0000 99%) no-repeat;
mask:
top var(--_g),
calc(50% + 50% * sin(var(--a)))
calc(50% - 50% * cos(var(--a))) var(--_g),
linear-gradient(#0000 0 0) content-box intersect,
conic-gradient(#000 var(--a), #0000 0);
} 

This variant applies border-radius to round the element and adds padding equal to the border thickness. The mask's radial-gradient is swapped for a linear-gradient with a single transparent color covering the content-box.

That approach adds two more variables but simplifies the gradients themselves — another valid way to reach the same shape.

See the Pen [Untitled](https://codepen.io/t_afif/pen/WNWErpV) by Temani Afif.

See the Pen Untitled by Temani Afif.

Dashed Circles and Rounded Tabs

Dashed circular edges reuse nearly identical code:

See the Pen [Dashed border](https://codepen.io/t_afif/pen/KKvjjZN) by Temani Afif.

See the Pen Dashed border by Temani Afif.

Two gradients do the work here: a black-to-transparent repeating-conic-gradient provides the dash pattern, while a transparent linear-gradient restricts visibility to the content-box. An intersect composition combines them.

mask:
 linear-gradient(#0000 0 0) content-box intersect,
 repeating-conic-gradient( /* ... */ );

For a deeper dive into how mask-composite behaves, Ana Tudor's "Mask Compositing: The Crash Course" is a solid resource.

Tabs present a more structured challenge. The rounded top corners are straightforward, but the inward-curving bottom edges require careful masking:

An empty light brown manilla folder.
Figure 11. (Large preview)

Positioning a pseudo-element behind the panel group is one approach, though it introduces fixed values and extra complexity. Masks keep the solution minimal and reusable.

Tab shape with rounded top edges and a gradient background with muted red colors.
Figure 12. (Large preview)
Illustrating the four steps to mask the shape.
Figure 13. (Large preview)

The process begins by adding borders — excluding the bottom — with border-radius on the top corners:

.tab {
  --r: 40px; /* radius size */

border: var(--r) solid #0000; /* transparent black */
border-bottom: 0;
border-radius: calc(2 * var(--r)) calc(2 * var(--r)) 0 0;
} 

The first mask layer displays only the padding area:

mask: linear-gradient(#000 0 0) padding-box;

Two radial gradients then reveal the bottom curves:

mask: 
  radial-gradient(100% 100% at 0 0, #0000 98%, #000) 0 100% / var(--r) var(--r), 
  radial-gradient(100% 100% at 100% 0, #0000 98%, #000) 100% 100% / var(--r) var(--r), 
  linear-gradient(#000 0 0) padding-box;
Showing the radial gradients used to create the shape’s inner curve.
Figure 14. (Large preview)

Full implementation:

.tab {
  --r: 40px; /* control the radius */

border: var(--r) solid #0000;
border-bottom: 0;
border-radius: calc(2 * var(--r)) calc(2 * var(--r)) 0 0;
mask:
radial-gradient(100% 100% at 0 0, #0000 98%, #000) 0 100% / var(--r) var(--r),
radial-gradient(100% 100% at 100% 0, #0000 98%, #000) 100% 100% / var(--r) var(--r),
linear-gradient(#000 0 0) padding-box;
mask-repeat: no-repeat;
background: linear-gradient(60deg, #BD5532, #601848) border-box;
} 

Notice the border-radius value in the complete setup:

border-radius: calc(2 * var(--r)) calc(2 * var(--r)) 0 0;

The rounded top edges equal two times the --r variable. Doubling is necessary because of the transparent border; the blue highlighted regions span 2 * R, while the red region measures 2 * R - R, or simply R.

An optimized version drops to two gradients — one linear, one radial — rather than three. See if you can determine how one gradient was eliminated.

See the Pen [Rounded tab using CSS mask](https://codepen.io/t_afif/pen/JjVpPmr) by Temani Afif.

See the Pen Rounded tab using CSS mask by Temani Afif.

Knowing when code can be tightened is genuinely difficult. There is no formula for spotting optimizations. Start with the most obvious solution, even if it uses many gradients, then refine with practice.

For practice, try applying the same masking logic to tooltip shapes, which position inward curves on the left, right, or both sides:

Six variations of tabs and tooltips with rounded corners and edges.
Figure 15. (Large preview)

Reference implementations are available in the online collection.

Shape Recipes: From Triangles to Floral Patterns

The techniques covered so far — gradients and mask for curves, clip-path for sharp edges — cover most of what you need. Applying them to specific shapes is often just a matter of configuration. Here is a rundown of common shapes, along with the formulas and resources that make them easy to reproduce.

Triangles

Triangles appear everywhere in UI work: play buttons, link arrows, accordion toggles. A basic triangle is a three-point polygon with dimensions set on the element:

Six triangle shape variations.
Figure 16. (Large preview)
.triangle {
  width: 200px;
  aspect-ratio: 1;
  clip-path: polygon(50% 0, 100% 100%, 0 100%);
}
Adding more polygon points produces border-only variants:

See the Pen [border-only triangle shapes](https://codepen.io/t_afif/pen/XWGzJpP) by Temani Afif.

See the Pen border-only triangle shapes by Temani Afif.
Rounded-corner triangles come from combining clip-path with mask:

See the Pen [Rounded triangles (the modern way)](https://codepen.io/t_afif/pen/QWovwoW) by Temani Afif.

See the Pen Rounded triangles (the modern way) by Temani Afif.
For the full range of triangle techniques with many examples, refer to the Verpex article, “CSS Shapes: The Triangle.”

Hearts

Hearts have traditionally required awkward hacks, but modern CSS reduces that to combining border-image with clip-path:

.heart {
  --c: red;

width: 200px;
aspect-ratio: 1;
border-image: radial-gradient(var(--c) 69%,#0000 70%) 84.5%/50%;
clip-path: polygon(-42% 0,50% 91%, 142% 0);
} 

See the Pen [Heart shape using border-image](https://codepen.io/t_afif/pen/MWPOJpP) by Temani Afif.

See the Pen Heart shape using border-image by Temani Afif.
The same approach works with mask-border to apply heart shapes directly to images:

See the Pen [CSS only heart images](https://codepen.io/t_afif/pen/PoRwjPM) by Temani Afif.

See the Pen CSS only heart images by Temani Afif.
A deeper treatment, with further variations, is in “CSS Shapes: The Heart” on the Verpex blog.

Ribbons

Ribbons are less of a single shape and more of a family — one with well over 100 variations in a dedicated collection. Rather than present one pattern, four articles cover the core mechanics and the range of possible designs:

Tooltips and Speech Bubbles

Tooltips and speech bubbles share the same open-ended design space — again, more than 100 possibilities exist in a dedicated collection. The essential patterns are thoroughly covered in:

Master those, and you can generate as many variations as you can dream up.

Cutting Corners and Cut-Outs

Removing corners from squares and rectangles creates attractive frames, useful either as decoration or as a backdrop for images. You can cut all corners or just some; the cuts can be sharp or rounded, and the overall shape can have an outline.

Squares with cut corners
Figure 17. (Large preview)
An online generator provides interactive code, and a dedicated article details each case.

A related idea is cutting an entire shape out of a rectangle — so-called inverted shapes. This is nothing new; it is just a clip-path with coordinates set in a polygon(). You already have that skill from the earlier examples in this guide:

Four cut-out shapes and the boxes they were cut out from.

See the Pen ["Cut-out shapes using clip-path"](https://codepen.io/t_afif/pen/gOJvdav) by Temani Afif ([@t_afif](https://codepen.io/t_afif))

See the Pen “Cut-out shapes using clip-path” by Temani Afif (@t_afif)
The details are fleshed out in “How To Create Cut-Out Shapes using The clip-path property” (Verpex Blog).

Section Dividers and Inner Curves

When you want visual transitions between page sections, one option is pair decorative borders that lock together:

Three examples of section dividers, one a narrow-angle, one a circular, and one a wide angle.
Figure 18. (Large preview)

The unifying pattern should by now be clear: whether you are clipping an element, masking it, or carving into it with gradients and coordinate points, the result is far more flexible than the workarounds of earlier CSS.

A full walkthrough of divider techniques can be found in “How to Create a Section Divider Using CSS” (freeCodeCamp).

Inner-curve shapes — known variously as inverted radius, notch, or bell curves — also rely on combining gradients within a mask to form variations:

Three vertically stacked rectangles with notched curves cut into the top and bottom edges, next to a red square with curves cut out of the middle.

Reference “How to create Shapes with Inner Curves using CSS Mask” (Verpex Blog) for the underlying logic, then see the technique in action:

See the Pen ["Inverted border-radius using CSS mask"](https://codepen.io/t_afif/pen/XWLJrWE) by Temani Afif ([@t_afif](https://codepen.io/t_afif))

See the Pen “Inverted border-radius using CSS mask” by Temani Afif (@t_afif)

See the Pen ["Fancy avatar header with hover effect"](https://codepen.io/t_afif/pen/oNrMJXL) by Temani Afif ([@t_afif](https://codepen.io/t_afif))

See the Pen “Fancy avatar header with hover effect” by Temani Afif (@t_afif)

Floral Shapes

Combine circles and waves and you get floral shapes:

Different flower-like shapes
Figure 19. (Large preview)

These do duty as decoration on their own but really shine when applied to images. Masking the edges of a photograph transforms it into a fancy custom frame. For instance, they power a pop-out hover effect in this demo:

See the Pen [Fancy Pop Out hover effect!](https://codepen.io/t_afif/pen/qBQzrwq) by Temani Afif.

See the Pen Fancy Pop Out hover effect! by Temani Afif.

Creating them involves trigonometric functions. If you want to understand the mathematics, two articles have all the details:

Wavy and Zig-Zag Boxes

Earlier examples put a wave or zig-zag on a single side. There is no need to stop there — the techniques extend to all four sides, creating full ornamented containers:

Four squares with wavy and zagged edges.

The recipe behind each is easy to parse with guidance:

A few demos show these boxes decorating images:

See the Pen ["Images inside wiggly boxes"](https://codepen.io/t_afif/pen/gbYBPma) by Temani Afif ([@t_afif](https://codepen.io/t_afif))

See the Pen “Images inside wiggly boxes” by Temani Afif (@t_afif)

See the Pen Images inside wavy boxes by Temani Afif (@t_afif) on CodePen.

See the Pen Images inside wavy boxes by Temani Afif (@t_afif) on CodePen.

See the Pen CSS-only Zig-Zag box by Temani Afif (@t_afif) on CodePen.

See the Pen CSS-only Zig-Zag box by Temani Afif (@t_afif) on CodePen.

Looking Back, Looking Forward

The set of shapes covered here is really a combinatorial system. Clipping, masking, gradients, composition, CSS variables — plus a few hidden features — account for hundreds of outcomes. In particular, three facts about polygon() unlock most of this:

  • It accepts coordinates outside the [0% 100%] range, which makes possible clipping paths that extend beyond the element box.
  • Swapping axes is a fast way to generate new variations of a shape.
  • Line segments in the polygon can intersect, opening up unexpected silhouettes.

No need to memorize snippets; understanding these mechanics is what lets you adapt the examples into any context. For quick reference and immediate source code, the CSS Shape website has the math already worked out for many shapes — and doubles as a springboard for inventing your own.

If you have a shape in mind that is not in the collection, consider adding it; the underlying logic makes most designs possible.

Reference Material

Smashing Editorial