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
Table of Content
- 1. What are the challenges?
- 2. How to approach it?
- 3. Let's Start Drawing
- 4. Conclusion
- 5. Related topics and inspirations
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.
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
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:
- 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.
- 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).
📝 Notes:
- The tool used in the GIFs above is: https://cssgradient.io/
- You can also use any CSS color keyword like
transparent
which we will use across this tutorial, but feel free to usergba()
orhsla()
functions and set thealpha
value to0
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.
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.
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.
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.
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.
3.1.5. Final Split
So, the final result of splitting the big shape into smaller simpler shapes should look like this.
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
);
📝 Notes:
- 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.
- 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 adding1px
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:
- The circle cutout is in the center.
- 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
);
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
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.
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));
📝 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. Thepillar-size
can never be less that the smallcircle-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)
);
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-gradient
s 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.
- The simple rounded corners
- 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;
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.
Top comments (6)
Nice explanation 👍
It's good to see people getting inspired by my articles/answers. 😊
If you are interested you can consider conic-gradient to reduce the number of gradients. instead of the 4 linear, you can only 2 conic. Here is my implementation for the same effect:
This a really nice enhancement to the article's approach, and thank you for your CSS-tricks post it was a good help for me because I faced this issue at work and your post was inspiring to approach it from a different angle.
I tried to post it on CSS tricks and mention you there somehow but unfortunately, they rejected my guest writing post there.
I searched for help to post it on CSS-tricks but failed to do so, currently, I am still trying to contact them to post it there.
Thanks again for such a great post.
Very Helpful, Thanks for sharing
So elegant
Awesome, Thanks for sharing such a good post 👏👏👏
really Fantastic 😧