How to make a Curved Cutout to any element

Karim Shalapy - Oct 21 '22 - - Dev Community

In many applications, we need to have a cutout from an element for stylistic reasons. Making a cutout is easy most of the time, but when it comes to having a curved cutout things start to get a bit complicated as there's no easy/straightforward way to do such a thing, so, let's talk about that!

We will talk about a specific example and how to create it, but that doesn't mean this is exclusive to this shape only, you can start drawing whatever shape you like using this technique hence an unlimited number of possible cutout shapes.

What we will discuss today is how to make a curved cutout that looks like this
Curved Cutout Final Result

Table of Content

1. What are the challenges?

We have two significant challenges that can prevent us from making this card.

1.1. Curves Challenge

As many of you may have figured out already, The curves on the cutout will be the most challenging part because it has many curves that are connected in different ways, making it impossible to create such curves with a simple set of CSS rules.

Curves Issue

1.2. Shadows Challenge

if we use box-shadow it will cause us some problems because of the curved cutout and it would look like this

Box Shadow Issue

This is happening because box shadow adds the shadow effect to your element's frame. And the frame here as we can see doesn't include the curve of our cutout.

The box-shadow CSS property adds shadow effects around an element's frame. You can set multiple effects separated by commas. A box shadow is described by X and Y offsets relative to the element, blur and spread radius, and color.

...read more

-- MDN Web Docs

2. How to approach it?

Now that we know our challenges we need to tackle them.

2.1. Curves Approach

As for the curves issue, we can approach this by making an SVG that looks like this and use it as our background. The issue with that (in most cases) is that the SVG will have a fixed aspect ratio, which means it will get deformed if we try to force any custom width and height on it. That said it can be done with SVGs but we won't be talking about that in this article.

One other way of approaching this issue is that we use gradients. Wait! What? Gradients?

Yes, some people only think about gradients as a progressive transition between two or more colors, and between the colors of the gradients, there's a blurred mix of the chosen colors. They're right, but not entirely.

The <gradient> CSS data type is a special type of <image> that consists of a progressive transition between two or more colors.

...read more

-- MDN Web Docs

In CSS gradients you can define any colors you want with any opacity required and put them at stop points these stop points let you know where this color starts and ends in this gradient. There are two tricks here that we can exploit:

  1. We can stack stop points on each other to have a harsh line. How is that going to help? Well, if the stop points are further away from each other this will create a spectrum of progressive colors between the two chosen colors, but if we stack the two stop points above each other we'll have a harsh line between the two colors, which means we can draw with these gradients. for example, if we want a circle, we can do a radial gradient with this trick and it'll draw a circle for use. Gradients Stop Points Stacking
  2. We can use any color with any >>> opacity <<<, This is huge because instead of having a gradient between two opaque colors we can have a gradient between the desired color and a transparent one which will open the possibility to draw any shape we want and make it have a transparent background (it will all make sense when we start applying this). Gradients between black and transparent

πŸ“ Notes:

  1. The tool used in the GIFs above is: https://cssgradient.io/
  2. You can also use any CSS color keyword like transparent which we will use across this tutorial, but feel free to use rgba() or hsla() functions and set the alpha value to 0 or play with its numbers to your liking, or use any preferred CSS color function.

2.2. Shadow Approach

Fortunately, the box-shadow issue can be solved easily nowadays thanks to the wide support of the CSS filter property.

Now, we can use the drop-shadow() function in the filter property to add content-aware shadows.

Meaning it doesn't follow the frame of the element, but it follows the outline of the content inside it stacked together, so if we use a gradient that creates a special kind of image as we mentioned earlier then the drop-shadow() will follow the outline of the generated image, so if this image has transparent parts it'll be taken into consideration by the drop-shadow() function unlike the box-shadow property.

Box Shadow VS Drop Shadow

The drop-shadow() CSS function applies a drop shadow effect to the input image. Its result is a <filter-function>.

...read more

-- MDN Web Docs

3. Let's Start Drawing

We discussed what challenges we might face and how to tackle them, then there's nothing left other than to start applying this to make everything clear.

3.1. Divide

Knowing that we have such simple geometrical shapes to draw with, such a curve needs to be broken down into these simple shapes first then.
So let's start breaking it down into these shapes.

3.1.1. Simple Rounded Corners

first we see the curves on the three other corners.

3 Simple Rounded Corners

We will ignore them from our process because these are simple enough to be done with a border-radius property.

Then, we focus on the main issue here, the top right corner (the one with the cutout), and let's start splitting it into smaller pieces.

3.1.2. Small Circles

If we look closely we'll see 2 small circles representing the top left and bottom right curves of the cutout.

Cutout's Small Circles

3.1.3. Big Circle Cutout

Also, we can see that there's a big square that has another bigger circle cutout from its corner.

Cutout's Big Circles

3.1.4. Fill Boxes

Then we'll quickly notice that we didn't consider the top and right sides, so let's add that too. Keep in mind this couldn't have been done with only one box because if it was one box it would hide the circle's curve behind it, so we had to make two boxes spanning till the edges and stopping at the center so that we would still have the circles' curves.

Cutout's Fill Boxes

3.1.5. Final Split

So, the final result of splitting the big shape into smaller simpler shapes should look like this.
Cutout's final split result

3.2. Conquer

Now, that we have split the complex shape into simpler geometrical shapes we can finally start coding. So let's begin drawing these shapes one by one.

3.2.1. Draw Big Circle Cutout

Here I will start with the most complex shape of the bunch, in my opinion, The big circle cutout.
Fortunately, thanks to the tricks I mentioned earlier with a little bit of tweaking we can make this shape with only one gradient.

So, how will we do this?
First, let's just draw a simple gradient that should look something like this.

background: radial-gradient(
  /* gradient shape */ circle,
  /* first color */ transparent /* first color stop point */ 30px,
  /* second color */ #fff /* second color stop point */ 31px
);
Enter fullscreen mode Exit fullscreen mode

πŸ“ Notes:

  1. I will use the CSS variables to store values that can be used again in the future, so from now on you'll see in the Pens variables all around instead of plain numbers.
  2. You'll see me add 1px to any variable or number used in the gradients stop points like: calc(var(--circle-r) + 1px). This is because if the two stop points are exactly the same the browser draws a jagged harsh line that's not anti-aliased, so to solve this we use this trick of adding 1px to every other stop point to add a little bit of blur and make the lines between the gradients colors smoother.

We will notice two things:

  1. The circle cutout is in the center.
  2. the white box is taking the full height and width of the element.

To fix that thankfully in the radial gradients we can specify where the center of the circle (where the gradient starts) can be by doing the following.

background: radial-gradient(
  circle at /* x-position */ 100% /* y-position */ 0%,
  transparent 30px,
  #fff 31px
);
Enter fullscreen mode Exit fullscreen mode

Here we will do a top-right cutout so we'll use the 100% 0% value to achieve that but you can play around with these numbers to get whatever position or curve you desire while keeping in mind that these values don't have to be in % it can be any CSS unit of measurement.

Also, for the full height and width issue, thanks to the background property we can specify the width, height, and position of each of the gradients we will draw. This is because as we said earlier that the gradient is a special type of image so we can treat it as such in the background property.

Now let's move the big box with the circle cutout to leave space for the smaller circles and the fill boxes like we've shown earlier in the divide section.

So, this should look something like this.

background:/* The gradient "special" image */ radial-gradient(
  circle at 100% 0%,
  transparent 30px,
  #fff 31px
);
/* bg-image x-position */ 0px /* bg-image y-position */ 34px / /* bg-image width */ calc(100% - 34px) /* bg-image height */ 100% /* bg-image repeat-x */ no-repeat /* bg-image repeat-y */no-repeat
Enter fullscreen mode Exit fullscreen mode

You can read more about the background shorthand property here

Now, this looks like the big circle cutout that we split in the divide section!

3.2.2. Draw The Small Circles

Now, this is a really easy one to draw we only need simple radial-gradients to draw the two circles, but to position them correctly we'll need to do some math (simple geometry). Don't be afraid, it's simple math, but if you don't feel like it, you can start playing with the numbers until you hit something you like, I will explain how to position the circles in this situation with a little bit of math because it will save me some time.

Knowing that the space between two circle centers is the same as the space between two circles' same edges, we can calculate the space between the small circles' centers as follows.

Space Between Small Circles

The space between circles circles-space equals the pillar-size and the big-circle-r.

So, we can apply this simple math equation like this:

--circles-space: calc(var(--big-circle-r) + var(--pillar-size));
Enter fullscreen mode Exit fullscreen mode

πŸ“ Notes:

The pillar-size is the top and right inset of the big circle cutout, it's the value we used to move the box to the bottom and left while drawing the big circle cutout. The pillar-size can never be less that the small circle-r.

Now, we know the space between the circles and we know they need to be drawn at the edges we can draw the circles as follows.

Notice that to draw a perfect circle I wrote it like that:

background: radial-gradient(
  circle closest-side,
  var(--card-color) 100%,
  transparent calc(100% + 1px)
);
Enter fullscreen mode Exit fullscreen mode

We needed to add the closest-side argument to the radial gradient to calculate the percentages from the closest side. you can read more about that here.

3.2.3. Draw The Fill Boxes

Now the result we have has empty spaces, these empty spaces should be filled with the fill boxes to finalize our drawing.
Notice that in the fill boxes section above, it looked like the boxes are small at the top to fill the gaps, but in the actual implementation, I will make their height or width take the full height or width respectively to overlap with other components.

This will prevent transparent lines from showing between the filling boxes and the other components due to a fraction in the position or size calculation.

Now let's draw the simple boxes using linear-gradients in the corresponding locations just like we did in the fill boxes section above.

3.2.4. Final Touches

Now the card is starting to come together, if you're paying attention then you'll notice that we're missing two things to do to finalize our design.

  1. The simple rounded corners
  2. The card shadow

Fortunately, These two points are really easy to achieve, as we can use the normal border-radius property to achieve rounded corners, and we can use the drop-shadow() function for the shadow as we discussed earlier in the shadow approach.

This should look something like this.

filter: drop-shadow(0 10px 48px rgba(21, 44, 115, 0.15));
border-radius: 50px 0 50px 50px;
Enter fullscreen mode Exit fullscreen mode

Now, we're done with the card and it looks exactly like the image. Also, I made a colorful variant to show you that the gradients drew the same shapes as the ones we created in the divide section.

4. Conclusion

We've made a card that has a somewhat complex curved cutout using gradients and by splitting the curve into smaller shapes that could be drawn we reached the final result.

Knowing the tricks and approaches I talked about in this article you can utilize them to make any kind of cutout shape or curve from any side or corner of the card.

As a matter of fact, you can go crazy with the design and draw whatever you need, and still have the background react with the height and width of your element dynamically without a fixed aspect ratio.

I hope this helped any of you out there, and now you have the result codepen, so you can play with the numbers to match your needs if you want.

5. Related topics and inspirations

  1. https://css-tricks.com/cut-corners-using-css-mask-and-clip-path-properties/
  2. https://stackoverflow.com/questions/57440523/css-transparent-curved-shape-with-two-rounded-sides
  3. https://stackoverflow.com/questions/61443008/how-to-create-a-curve-on-the-top-of-a-background
. .