How to create Shapes with Inner Curves using CSS Mask

Written by Web Developer

September 25, 2024
How to create Shapes with Inner Curves using CSS Mask

Inverted radius, notch, rounded cut, bell curve, etc. A lot of names can be used to describe the shapes we are going to create in this article. I also call them inner curves but I guess a figure worth thousand words.

CSS-only shapes (inverted radius, inner curves)

Naming those shapes is not that easy, same as creating them using CSS. Most of the time we reach for many elements/pseudo-elements and we try to stack them in a way to simulate the curvature. It’s a bit hacky, not flexible and it’s a lot of magic numbers everywhere! I will show you how to create those CSS shapes using one element and a flexible code that you can easily reuse.

Before we start, you can already find the code of the shapes within my online collection but stay with me to know the secret behind creating them.

Inverted Radius shape

Let’s start with the first shape. The idea is to cut the corner of an element while having smooth corners.

CSS only inverted radius shape

Cutting the corner of an element is an easy task. All that you need is one gradient:

mask: radial-gradient(40px at top right,#0000 98%,#000)

That’s all! I also have an online generator where you can find the different combination. And if you want to dig into the detail, check my article “Tricks to Cut Corners Using CSS Mask and Clip-Path Properties”.

If you are not familiar with mask and gradients, I highly advice you to take the time to read the linked article and study the simple case of cutting a corner. The shape we are going to create will use the same technique but with a more complex gradient configuration.

Here is a step-by-step illustration of the gradient configuration

Illustration of the gradient configuration for the inverted radius shape

First, we use the same radial-gradient we used to cut the top-right corner. The only difference is an offset from the top and right edges. Then, we consider a second gradient (a conic-gradient this time) to fill the space created by the shift of previous gradient while keeping some space for the curvature. Finally, we add two radial-gradient to fill the remaining space and simulate the curvature.

I am using three different colors to illustrate the puzzle. The colored part is what will be visible from the element while the remaining will be the transparent part.

Inverted radius variables

Two variables will control our shape. --r will be the radius of the small circle (the curvature) and --s will be the size of the cut.

Let’s translate this into some code:

.shape {
  --r: 25px; /* the radius */
  --s: 40px; /* the size of the cut */
  
  --_m:/calc(2*var(--r)) calc(2*var(--r))
    radial-gradient(#000 70%,#0000 72%) no-repeat;
  mask:
    right calc(var(--s) + var(--r)) top 0 var(--_m),
    right calc(var(--s) + var(--r)) var(--_m),
    radial-gradient(var(--s) at 100% 0,#0000 99%,#000 101%) 
     calc(-1*var(--r)) var(--r) no-repeat,
    conic-gradient(at calc(100% - var(--s) - 2*var(--r)) calc(var(--s) + 2*var(--r)), #0000 25%,#000 0);
}

The mask property contains four gradients like described previously. Two gradients are identical (the small circles) that’s why I am using the variable --_m to avoid the repetition.

But the code is a bit complex and not easy to memorize…

You don’t need to memorize anything! All you have to do is copy the code and use it. The idea is to understand the logic behind creating the shape not to memorize the code. After all, you don’t have to touch the code since all you have to do is to update a few variables to control the shape. I told you we are going to create a flexible code easy to reuse!

This said, it’s also good to be able to write some CSS alone so here is a homework for you. Can you adjust the previous code to create the other variations? We did an inverted radius at the top-right corner and we can use the same code structure for the other corners.

Different variations for the inverted radius shape

You can always find the code within my online collection and here is a demo where I am using the different variations with image elements:

Try to use the same process I described: one gradient at a time until you get the final shape. It’s a bit tricky at first but we get used to it and maybe you will find a different implementation than mine.

Notch Shape

Let’s move to the next shape. This time, the cut will be on the sides instead of the corners.

CSS-only notch shape

Like the previous shape, creating a circular cut is as as simple as using one gradient (code also available at: https://css-shape.com/circle-cut/)

mask: radial-gradient(40px at top,#0000 98%,#000)

But to have the curvature part we need more gradients. The same number of gradients as the previous shape since the logic is the same.

Illustration of the notch shape

I think we can skip the explanation for this one. Like the inverted radius, we have one gradient for the cut, one gradient to fill the extra space and two gradients for the curvature.

The variables of the notch shape

Even the variables are the same but this time I will introduce a third variable that will control the depth of the curve.

The shape is built using three circles that are aligned (one circle for the cut and two circles for the curvature). We can adjust the alignment to add more control to the shape like illustrated below:

Illustration of the depth variable

On the left, all the circles are aligned horizontally but on the right the big circle is shifted to the top. The circles are still adjacent but no more aligned. I drew a line to illustrate the alignment and the angle of rotation of that line will be our new variable.

.shape {
  --r: 20px; /* control the rounded part */
  --s: 40px; /* control the size of the cut */
  --a: 40deg; /* control the depth of the curvature */
  
  --_m:0/calc(2*var(--r)) calc(2*var(--r)) no-repeat
    radial-gradient(50% 50%,#000 calc(100% - 1px),#0000);
  --_d:(var(--s) + var(--r))*cos(var(--a));
  mask:
    calc(50% + var(--_d)) var(--_m),calc(50% - var(--_d)) var(--_m),
    radial-gradient(var(--s) at 50% calc(-1*sin(var(--a))*var(--s)),
      #0000 100%,#000 calc(100% + 1px)) 0 calc(var(--r)*(1 - sin(var(--a)))) no-repeat,
    linear-gradient(90deg,#000 calc(50% - var(--_d)),#0000 0 calc(50% + var(--_d)),#000 0);
}

I know, the code is more complex for this shape but we have our variables so we don’t really care about the complexity. Do you think I will remember that code? Not at all. I will go to my online collection to copy it and adjust the variables to fit my use case.

What is good with modern CSS is that we can easily create reusable code. You make the effort once and you have a code that you can use as many time as you want. No more magic numbers and hacky tricks that work only for particular cases!

Here is the full code showing the top and bottom variations. Can you figure out how to create the bottom version before checking my code? Another exercise to practice with gradients.

And what about a fancy animation? Hover the below and see the nice effect you get!

Cool right? All that I am doing is animating one variable (the new one that controls the depth). That’s all!

Conclusion

Using CSS mask and different combinations of gradients we created two common shapes and produced a flexible code. Worth noting that we relied on a single element and no pseudo-elements so you can have a gradient coloration and apply the shape to image elements as well.

Don’t forget to bookmark my collection of CSS Shapes and check my previous articles if you want to learn how to create more CSS shapes:

Discount

Enter Discount Code MOVEME During The SIGN UP Process To Get 90% Off Your First Month

with the discount code

MOVEME

Use Code Now
Jivo Live Chat