The Modern Guide For Making CSS Shapes

The Modern Guide For Making CSS Shapes

You have for sure googled “how to create [shape_name] with CSS” at least once in your front-end career if it’s not something you already have bookmarked. And the number of articles and demos you will find out there is endless.

Good, right? Copy that code and drop it into the ol’ stylesheet. Ship it!

The problem is that you don’t understand how the copied code works. Sure, it got the job done, but many of the most widely used CSS shape snippets are often dated and rely on things like magic numbers to get the shapes just right. So, the next time you go into the code needing to make a change to it, it either makes little sense or is inflexible to the point that you need an entirely new solution.

So, here it is, your one-stop modern guide for how to create shapes in CSS! We are going to explore the most common CSS shapes while highlighting different CSS tricks and techniques that you can easily re-purpose for any kind of shape. The goal is not to learn how to create specific shapes but rather to understand the modern tricks that allow you to create any kind of shape you want.

Table of Contents

You can jump directly to the topic you’re interested in to find relevant shapes or browse the complete list. Enjoy!

  • Hexagons

  • Octagons
  • Stars
  • Polygons & Starbursts
  • Parallelograms & Trapezoids
  • Circles & Holes
  • Border Edges
  • Rounded Arcs
  • Dashed Circles
  • Rounded Tabs
  • Triangles
  • Hearts
  • Ribbons
  • Tooltips & Speech Bubbles
  • Cutting Corners
  • Section Dividers
  • Floral Shapes
Why Not SVG?

I get asked this question often, and my answer is always the same: Use SVG if you can! I have nothing against SVG. It’s just another approach for creating shapes using another syntax with another set of considerations. If SVG was my expertise, then I would be writing about that instead!

CSS is my field of expertise, so that’s the approach we’re covering for drawing shapes with code. Choosing CSS or SVG is typically a matter of choice. There may very well be a good reason why SVG is a better fit for your specific needs.

Many times, CSS will be your best bet for decorative things or when you’re working with a specific element in the markup that contains real content to be styled. Ultimately, though, you will need to consider what your project’s requirements are and decide whether a CSS shape is really what you are looking for.

Your First Resource

Before we start digging into code, please spend a few minutes over at my CSS Shape website. You will find many examples of CSS-only shapes. This is an ever-growing collection that I regularly maintain with new shapes and techniques. Bookmark it and use it as a reference as we make our way through this guide.

Is it fairly easy to modify and tweak the CSS for those shapes?

Yes! The CSS for each and every shape is optimized to be as flexible and efficient as possible. The CSS typically targets a single HTML element to prevent you from having to touch too much markup besides dropping the element on the page. Additionally, I make liberal use of CSS variables that allow you to modify things easily for your needs.

Most of you don't have time to grasp all the techniques and tricks to create different shapes, so an online resource with ready-to-use snippets of code can be a lifesaver!

Clipping Shapes In CSS

The CSS clip-path property — and its polygon() function — is what we commonly reach for when creating CSS Shapes. Through the creation of common CSS shapes, we will learn a few tricks that can help you create other shapes easily.

Hexagons

Let’s start with one of the easiest shapes; the hexagon. We first define the shape’s dimensions, then provide the coordinates for the six points and we are done.

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

We’re basically drawing the shape of a diamond where two of the points are set way outside the bounds of the hexagon we’re trying to make. This is perhaps the very first lesson for drawing CSS shapes: Allow yourself to think outside the box — or at least the shape’s boundaries.

Look how much simpler the code already looks:

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

Did you notice that I updated the aspect-ratio property in there? I’m using a trigonometric function, cos(), to replace the magic number 0.866. The exact value of the ratio is equal to cos(30deg) (or sin(60deg)). Besides, cos(30deg) is a lot easier to remember than 0.866.

Here’s something fun we can do: swap the X and Y coordinate values. In other words, let’s change the polygon() coordinates from this pattern:

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

…to this, where the Y values come before the X values:

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

What we get is a new variation of the hexagon:

I know that visualizing the shape with outside points can be somewhat difficult because we’re practically turning the concept of clipping on its head. But with some practice, you get used to this mental model and develop muscle memory for it.

Notice that the CSS is remarkably similar to what we used to create a hexagon:

.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))
  );
}

Except for the small trigonometric formula, the structure of the code is identical to the last hexagon shape — set the shape’s dimensions, then clip the points. And notice how I saved the math calculation as a CSS variable to avoid repeating that code.

If math isn’t really your thing — and that’s totally fine! — remember that the formulas are simply one part of the puzzle. There’s no need to go back to your high school geometry textbooks. You can always find the formulas you need for specific shapes in my online collection. Again, that collection is your first resource for creating CSS shapes!

And, of course, we can apply this shape to an <img> element as easily as we can a <div>:

It may sound impossible to make a star out of only five points, but it’s perfectly possible, and the trick is how the points inside polygon() are ordered. If we were to draw a star with pencil on paper in a single continuous line, we would follow the following order:

It’s the same way we used to draw stars as kids — and it fits perfectly in CSS with polygon()! This is another hidden trick about clip-path with polygon(), and it leads to another key lesson for drawing CSS shapes: the lines we establish can intersect. Again, we’re sort of turning a concept on its head, even if it’s a pattern we all grew up making by hand.

Here’s how those five points translate to CSS:

.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) */ ); }

The funny thing is that starbursts are basically the exact same thing as polygons, just with half the points that we can move inward.

Figure 6.

I often advise people to use my online generators for shapes like these because the clip-path coordinates can get tricky to write and calculate by hand.

  • Polygon generator
  • Starburst generator

That said, I really believe it’s still a very good idea to understand how the coordinates are calculated and how they affect the overall shape. I have an entire article on the topic for you to learn the nuances of calculating coordinates.

Parallelograms & Trapezoids

Another common shape we always build is a rectangle shape where we have one or two slanted sides. They have a lot of names depending on the final result (e.g., parallelogram, trapezoid, skewed rectangle, and so on), but all of them are built using the same CSS technique.

First, we start by creating a basic rectangle by linking the four corner points together:

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

This code produces nothing because our element is already a rectangle. Also, note that 0 and 100% are the only values we’re using.

Next, offset some values to get the shape you want. Let’s say our offset needs to be equal to 10px. If the value is 0, we update it with 10px, and if it’s 100% we update it with calc(100% - 10px). As simple as that!

But which value do I need to update and when?

Try and see! Open your browser’s developer tools and update the values in real-time to see how the shape changes, and you will understand what points you need to update. I would lie if I told you that I write all the shapes from memory without making any mistakes. In most cases, I start with the basic rectangle, and I add or update points until I get the shape I want. Try this as a small homework exercise and create the shapes in Figure 11 by yourself. You can still find all the correct code in my online collection for reference.

If you want more CSS tricks around the clip-path property, check my article “CSS Tricks To Master The clip-path Property” which is a good follow-up to this section.

Masking Shapes In CSS

We just worked with a number of shapes that required us to figure out a number of points and clip-path by plotting their coordinates in a polygon(). In this section, we will cover circular and curvy shapes while introducing the other property you will use the most when creating CSS shapes: the mask property.

Like the previous section, we will create some shapes while highlighting the main tricks you need to know. Don’t forget that the goal is not to learn how to create specific shapes but to learn the tricks that allow you to create any kind of shape.

Circles & Holes

When talking about the mask property, gradients are certain to come up. We can, for example, “cut” (but really “mask”) a circular hole out of an element with a radial-gradient:

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

Why aren’t we using a simple background instead? The mask property allows us more flexibility, like using any color we want and applying the effect on a variety of other elements, such as <img>. If the color and flexible utility aren’t a big deal, then you can certainly reach for the background property instead of cutting a hole.

Here’s the mask working on both a <div> and <img>:

Once again, it’s all about CSS masks and gradients. In the following articles, I provide you with examples and recipes for many different possibilities:

  • “Fancy CSS Borders Using Masks” (CSS-Tricks)
  • “How to Create Wavy Shapes & Patterns in CSS” (CSS-Tricks)

Be sure to make it to the end of the second article to see how this technique can be used as decorative background patterns.

This time, we are going to introduce another technique which is “composition”. It’s an operation we perform between two gradient layers. We either use mask-composite to define it, or we declare the values on the mask property.

The figure below illustrates the gradient configuration and the composition between each layer.

We start with a radial-gradient to create a full circle shape. Then we use a conic-gradient to create the shape below it. Between the two gradients, we perform an “intersect” composition to get the unclosed circle. Then we tack on two more radial gradients to the mask to get those nice rounded endpoints on the unclosed circle. This time we consider the default composition, “add”.

Gradients aren’t something new as we use them a lot with the background property but “composition” is the new concept I want you to keep in mind. It’s a very handy one that unlocks a lot of possibilities.

Ready for the CSS?

.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) }

We could get clever and use a pseudo-element for the shape that’s positioned behind the set of panels, but that introduces more complexity and fixed values than we ought to have. Instead, we can continue using CSS masks to get the perfect shape with a minimal amount of reusable code.

It’s not really the rounded top edges that are difficult to pull off, but the bottom portion that curves inwards instead of rounding in like the top. And even then, we already know the secret sauce: using CSS masks by combining gradients that reveal just the parts we want.

We start by adding a border around the element — excluding the bottom edge — and applying a border-radius on the top-left and top-right 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;
}

Next, we add the first mask layer. We only want to show the padding area (i.e., the red area highlighted in Figure 10).

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

Let’s add two more gradients, both radial, to show those 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;

Here is how the full code comes together:

.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;
}

As usual, all it takes is one variable to control the shape. Let’s zero-in on the border-radius declaration for a moment:

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

Notice that the shape’s rounded top edges are equal to two times the radius (--r) value. If you’re wondering why we need a calculation here at all, it’s because we have a transparent border hanging out there, and we need to double the radius to account for it. The radius of the blue areas highlighted in Figure 13 is equal to 2 * R while the red area highlighted in the same figure is equal to 2 * R - R, or simply R.

We can actually optimize the code so that we only need two gradients — one linear and one radial — instead of three. I’ll drop that into the following demo for you to pick apart. Can you figure out how we were able to eliminate one of the gradients?

I’ll throw in two additional variations for you to investigate:

These aren’t tabs at all but tooltips! We can absolutely use the exact same masking technique we used to create the tabs for these shapes. Notice how the curves that go inward are consistent in each shape, no matter if they are positioned on the left, right, or both.

You can always find the code over at my online collection if you want to reference it.

More CSS Shapes

At this point, we’ve seen the main tricks to create CSS shapes. You will rely on mask and gradients if you have curves and rounded parts or clip-path when there are no curves. It sounds simple but there’s still more to learn, so I am going to provide a few more common shapes for you to explore.

Instead of going into a detailed explanation of the shapes in this section, I’m going to give you the recipes for how to make them and all of the ingredients you need to make it happen. In fact, I have written other articles that are directly related to everything we are about to cover and will link them up so that you have guides you can reference in your work.

Triangles

A triangle is likely the first shape that you will ever need. They’re used in lots of places, from play buttons for videos, to decorative icons in links, to active state indicators, to open/close toggles in accordions, to… the list goes on.

Creating a triangle shape is as simple as using a 3-point polygon in addition to defining the size:

.triangle {
  width: 200px;
  aspect-ratio: 1;
  clip-path: polygon(50% 0, 100% 100%, 0 100%);
}

But we can get even further by adding more points to have border-only variations:

We can cut all the corners or just specific ones. We can make circular cuts or sharp ones. We can even create an outline of the overall shape. Take a look at my online generator to play with the code, and check out my full article on the topic where I am detailing all the different cases.

Section Dividers

Speaking of visual transitions between sections, what if both sections have decorative borders that fit together like a puzzle?

I hope you see the pattern now: sometimes, we’re clipping an element or masking portions of it. The fact that we can sort of “carve” into things this way using polygon() coordinates and gradients opens up so many possibilities that would have required clever workarounds and super-specific code in years past.

See my article “How to Create a Section Divider Using CSS” on the freeCodeCamp blog for a deep dive into the concepts, which we’ve also covered here quite extensively already in earlier sections.

Floral Shapes

We’ve created circles. We’ve made wave shapes. Let’s combine those two ideas together to create floral shapes.

These shapes are pretty cool on their own. But like a few of the other shapes we’ve covered, this one works extremely well with images. If you need something fancier than the typical box, then masking the edges can come off like a custom-framed photo.

Here is a demo where I am using such shapes to create a fancy hover effect:

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

There’s a lot of math involved with this, specifically trigonometric functions. I have a two-part series that gets into the weeds if you’re interested in that side of things:

  • “Creating Flower Shapes using CSS Mask & Trigonometric Functions” (Frontend Masters)
  • “Creating Wavy Circles with Fancy Animations in CSS” (Frontend Masters)

As always, remember that my online collection is your Number One resource for all things related to CSS shapes. The math has already been worked out for your convenience, but you also have the references you need to understand how it works under the hood.

Conclusion

I hope you see CSS Shapes differently now as a result of reading this comprehensive guide. We covered a few shapes, but really, it’s hundreds upon hundreds of shapes because you see how flexible they are to configure into a slew of variations.

At the end of the day, all of the shapes use some combination of different CSS concepts such as clipping, masking, composition, gradients, CSS variables, and so on. Not to mention a few hidden tricks like the one related to the polygon() function:

  • It accepts points outside the [0% 100%] range.
  • Switching axes is a solid approach for creating shape variations.
  • The lines we establish can intersect.

It’s not that many things, right? We looked at each of these in great detail and then whipped through the shapes to demonstrate how the concepts come together. It’s not so much about memorizing snippets than it is thoroughly understanding how CSS works and leveraging its features to produce any number of things, like shapes.

Don’t forget to bookmark my CSS Shape website and use it as a reference as well as a quick stop to get a specific shape you need for a project. I avoid re-inventing the wheel in my work, and the online collection is your wheel for snagging shapes made with pure CSS.

Please also use it as inspiration for your own shape-shifting experiments. And post a comment if you think of a shape that would be a nice addition to the collection.

References

  • “CSS Shapes: Polygon & Starburst” (Verpex Blog)
  • “CSS Tricks To Master The clip-path Property” (Verpex Blog)
  • “Fancy CSS Borders Using Masks” (CSS-Tricks)
  • “How to Create Wavy Shapes & Patterns in CSS” (CSS-Tricks)
  • “CSS Shapes: The Triangle” (Verpex Blog)
  • “CSS Shapes: The Heart” (Verpex Blog)
  • “CSS Responsive Multi-Line Ribbon Shapes, Part 1” (Smashing Magazine)
  • “CSS Responsive Multi-Line Ribbon Shapes, Part 2” (Smashing Magazine)
  • “CSS Shapes: The Ribbon” (Verpex Blog)
  • “How to Create CSS Ribbon Shapes with a Single Element” (SitePoint)
  • “Modern CSS Tooltips And Speech Bubbles, Part 1” (Smashing Magazine)
  • “Modern CSS Tooltips And Speech Bubbles, Part 2” (Smashing Magazine)
  • “Tricks to Cut Corners Using CSS Mask and Clip-Path Properties”
  • “How to Create a Section Divider Using CSS” (freeCodeCamp Blog)
  • “Re-Creating The Pop-Out Hover Effect With Modern CSS (Part 1)” (Smashing Magazine)
  • “Creating Flower Shapes using CSS Mask & Trigonometric Functions” (Frontend Masters)
  • “Creating Wavy Circles with Fancy Animations in CSS” (Frontend Masters)
  • “Mask Compositing: The Crash Course” by Ana Tudor (CSS-Tricks)