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.

Inverted radius shape with offsets

We can upgrade the previous shape by considering more variables to control the offsets. Actually we can only control the curvature but it's better if we can move the curvature like below for example:

Overview of the offset

The above figure illustrates a horizontal offset but we can also have a vertical one as well. For this, we will introduce a fourth gradient and adjust the configuration slightly.

Overview of the gradients

Previously we used one conic-gradient but for this shape, we use two conic-gradient as illustrated in (1). Both gradients have the same configuration, they simply differ by their position which is controlled by the new offset variables:

.inverted-radius {
  --r: 20px; /* the radius */
  --s: 30px; /* size of inner curve */
  --x: 50px; /* horizontal offset (no percentage) */
  --y: 10px; /* vertical offset (no percentage) */
  
  --_g:conic-gradient(at calc(100% - var(--r)) var(--r),#0000 25%,#000 0);
  --_d:(var(--s) + var(--r));
  mask:
    var(--_g) calc(-1*var(--_d) - var(--x)) 0,
    var(--_g) 0 calc(var(--_d) + var(--y));
  mask-repeat: no-repeat;
}

Then we use the same radial-gradient for the curvatures. One gradient for the inner curve and two others to create the small circles on the corners.

.inverted-radius {
  --r: 20px; /* the radius */
  --s: 30px; /* size of inner curve */
  --x: 50px; /* horizontal offset (no percentage) */
  --y: 10px; /* vertical offset (no percentage) */
  
  --_m:/calc(2*var(--r)) calc(2*var(--r)) radial-gradient(#000 70%,#0000 72%);
  --_g:conic-gradient(at calc(100% - var(--r)) var(--r),#0000 25%,#000 0);
  --_d:(var(--s) + var(--r));
  mask:
    calc(100% - var(--_d) - var(--x)) 0 var(--_m),
    100% calc(var(--_d) + var(--y)) var(--_m),
    radial-gradient(var(--s) at 100% 0,#0000 99%,#000 calc(100% + 1px)) 
     calc(-1*var(--r) - var(--x)) calc(var(--r) + var(--y)),
    var(--_g) calc(-1*var(--_d) - var(--x)) 0,
    var(--_g) 0 calc(var(--_d) + var(--y));
  mask-repeat: no-repeat;
}

The code looks more complex but all you have to do is to adjust the variables to control shape. There is no need to learn everything by heart! Here is a demo where I am using the previous code to create a cool hover effect. All that I am doing is animating the horizontal offset:

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 other 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