
CSS Blob Recipes
CSS Blob Recipes êŽë š
Blob, Blob, Blob. You hate them. You love them. Personally, as a design illiterate, I like to overuse them⊠a lot. And when you repeat the same process over and over again, itâs only a question of how much you can optimize it, or in this case, whatâs the easiest way to create blobs in CSS? Turns out, as always, there are many approaches.
To know if our following blobs are worth using, weâll need them to pass three tests:
- They can be with just a single element (and preferably without pseudos).
- They can be easily designed (ideally through an online tool).
- We can use gradient backgrounds, borders, shadows, and other CSS effects on them.
Without further ado, letâs Blob, Blob, Blob right in.
Just generate them online
I know itâs disenchanting to click on an article about making blobs in CSS just for me to say you can generate them outside CSS. Still, itâs probably the most common way to create blobs on the web, so to be thorough, these are some online tools Iâve used before to create SVG blobs.
- Haikei. Probably the one I have used the most since, besides blobs, it can also generate lots of SVG backgrounds.
- Blobmaker. A dedicated tool for making blobs. Itâs apparently part of Haikei now, so you can use both.
- Lastly, almost all graphic programs let you hand-draw blobs and export them as SVGs.
For example, this is one I generated just now. Keep it around, as it will come in handy later.

<svg viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
<path
fill="#FA4D56"
d="M65.4,-37.9C79.2,-13.9,81,17,68.1,38C55.2,59.1,27.6,70.5,1.5,69.6C-24.6,68.8,-49.3,55.7,-56,38.2C-62.6,20.7,-51.3,-1.2,-39,-24.4C-26.7,-47.6,-13.3,-72,6.2,-75.6C25.8,-79.2,51.6,-62,65.4,-37.9Z"
transform="translate(100 100)"
/>
</svg>
Using border-radius
While counterintuitive, we can use the border-radius
` property to create blobs. This technique isnât new by any means; it was first described by Nils Binder in 2018, but it is still fairly unknown. Even for those who use it, the inner workings are not entirely clear.
To start, you may know the border-radius
 is a shorthand to each individual cornerâs radius, going from the top left corner clockwise. For example, we can set each cornerâs border-radius
 to get a bubbly square shape:
<div class="blob"></div>
.blob {
border-radius: 25% 50% 75% 100%;
}
However, what border-radius
 does â and also why itâs called âradiusâ â is to shape each corner following a circle of the given radius. For example, if we set the top left corner to 25%
, it will follow a circle with a radius 25%
 the size of the shape.
.blob {
border-top-left-radius: 25%;
}
Whatâs less known is that each corner property is still a shortcut towards its horizontal and vertical radii. Normally, you set both radii to the same value, getting a circle, but you can set them individually to create an ellipse. For example, the following sets the horizontal radius to 25%
 of the elementâs width and the vertical to 50%
 of its height:
.blob {
border-top-left-radius: 25% 50%;
}
We can now shape each corner like an ellipse, and it is the combination of all four ellipses that creates the illusion of a blob! Just take into consideration that to use the horizontal and vertical radii syntax through the border-radius
 property, weâll need to separate the horizontal from the vertical radii using a forward slash (/
).
.blob {
border-radius:
/* horizontal */
100% 30% 60% 70% /
/* vertical */
50% 40% 70% 70%;
}
The syntax isnât too intuitive, so designing a blob from scratch will likely be a headache. Luckily, Nils Binder made a tool exactly for that!
Blobbing blobs together
This hack is awesome. We arenât supposed to use border-radius
 like that, but we still do. Admittedly, we are limited to boring blobs. Due to the nature of border-radius
, no matter how hard we try, we will only get convex shapes.

Just going off border-radius
, we can try to minimize it a little by sticking more than one blob together:
However, I donât want to spend too much time on this technique since it is too impractical to be worth it. To name a few drawbacks:
- We are using more than one element or, at the very least, an extra pseudo-element. Ideally, we want to keep it to one element.
- We donât have a tool to prototype our blobby amalgamations, so making one is a process of trial and error.
- We canât use borders, gradients, or box shadows since they would reveal the elementâs outlines.
Multiple backgrounds and SVG filters
This one is an improvement in the Gooey Effect, described here by Lucas Bebber, although I donât know who first came up with it. In the original effect, several elements can be morphed together like drops of liquid sticking to and flowing out of each other:
It works by first blurring shapes nearby, creating some connected shadows. Then we crank up the contrast, forcing the blur out and smoothly connecting them in the process. Take, for example, this demo by Chris Coyer (Itâs from 2014, so more than 10 years ago!):
If you look at the code, youâll notice Chris uses the filter
 property along the blur()
 and contrast()
 functions, which Iâve also seen in other blob demos. To be specific, it applies blur()
 on each individual circle and then contrast()
 on the parent element. So, if we have the following HTML:
<div class="blob">
<div class="subblob"></div>
<div class="subblob"></div>
<div class="subblob"></div>
</div>
âŠwe would need to apply filters and background colors as such:
.blob {
filter: contrast(50);
background: white; /* Solid colors are necessary */
}
.subblob {
filter: blur(15px);
background: black; /* Solid colors are necessary */
}
However, there is a good reason why those demos stick to white shapes and black backgrounds (or vice versa) since things get unpredictable once colors arenât contrast-y enough. See it for yourself in the following demo by changing the color. Just be wary:Â shades get ugly.
To solve this, we will use an SVG filter instead. I donât want to get too technical on SVG (if you want to, read Lucaâs post!). In a nutshell, we can apply blurring and contrast filters using SVGs, but now, we can also pick which color channel we apply the contrast to, unlike normal contrast()
, which modifies all colors.
Since we want to leave color channels (R
, G
 and B
) untouched, we will only crank the contrast up for the alpha channel. That translates to the next SVG filter, which can be embedded in the HTML:
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" style="position: absolute;">
<defs>
<filter id="blob">
<feGaussianBlur in="SourceGraphic" stdDeviation="12" result="blur" />
<feColorMatrix in="blur" mode="matrix" values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 18 -6" result="goo" />
<feBlend in="SourceGraphic" in2="blob" />
</filter>
</defs>
</svg>
To apply it, we will use again filter
, but this time weâll set it to url("#blob")
, so that it pulls the SVG from the HTML.
.blob {
filter: url("#blob");
}
And now we can even use it with gradient backgrounds!
That being said, this approach comes with two small, but important, changes to common CSS filters:
- The filter is applied to the parent element, not the individual shapes.
- The parent element must be transparent (which is a huge advantage). To change the background color, we can instead change the body or other ancestorsâ background, and it will work with no issues.
Whatâs left is to place the .subblob
 elements together such that they make a blobby enough shape, then apply the SVG filters to morph them:
Making it one element
This works well, but it has a similar issue to the blob we made by morphing several border-radius
instances: too many elements for a simple blob. Luckily, we can take advantage of the background
 property to create multiple shapes and morph them together using SVG filters, all in a single element. Since we are keeping it to one element, we will go back to just one empty .blob
div:
<div class="blob"></div>
To recap, the background
 shorthand can set all background properties and also set multiple backgrounds at once. Of all the properties, we only care about the background-image
, background-position
 and background-size
.
First, we will use background-image
 along with radial-gradient()
 to create a circle inside the element:
body {
background: radial-gradient(farthest-side, var(--blob-color) 100%, #0000);
background-repeat: no-repeat; /* Important! */
}
Here is what each parameter does:
farthest-side
: Confines the shape to the elementâs box farthest from its center. This way, it is kept as a circle.var(--blob-color) 100%
: Fills the background shape from 0 to 100% with the same color, so it ends up as a solid color.#0000
: After the shape is done, it makes a full stop to transparency, so the color ends.
The next part is moving and resizing the circle using the background-position
 and background-size
 properties. Luckily, both can be set on background
 after the gradient, separated from each other by a forward slash (/
).
body {
background: radial-gradient(...) 20% 30% / 30% 40%;
background-repeat: no-repeat; /* Important! */
}
The first pair of percentages sets the shapeâs horizontal and vertical position (taking as a reference the top-left corner), while the second pair sets the shapeâs width and height (taking as a reference the elementâs size).
As I mentioned, we can stack up different backgrounds together, which means we can create as many circles/ellipses as we want! For example, we can create three ellipses on the same element:
.blob {
background:
radial-gradient(farthest-side, var(--blob-color) 100%, #0000) 20% 30% / 30% 40%,
radial-gradient(farthest-side, var(--blob-color) 100%, #0000) 80% 50% / 40% 60%,
radial-gradient(farthest-side, var(--blob-color) 100%, #0000) 50% 70% / 50% 50%;
background-repeat: no-repeat;
}
Whatâs even better is that SVG filters donât care whether shapes are made of elements or backgrounds, so we can also morph them together using the last url(#blob)
 filter!
While this method may be a little too much for blobs, it unlocks squishing, stretching, dividing, and merging blobs in seamless animations.
Again, all these tricks are awesome, but not enough for what we want! We accomplished reducing the blob to a single element, but we still canât use gradients, borders, or shadows on them, and also, they are tedious to design and model. Then, that brings us to the ultimate blob approachâŠ
Using the shape()
function
Fortunately, there is a new way to make blobs that just dropped to CSS: the shape()
 function!
Iâll explain shape()
âs syntax briefly, but for an in-depth explanation, youâll want to check out both this explainer from the CSS-Tricks Almanac as well as Temani Afifâs three-part series on the shape()
 function, as well as his recent article about blobs.
First off, the CSSÂ shape()
 function is used alongside the clip-path
 property to cut elements into any shape we want. More specifically, it uses a verbal version of SVGâs path
 syntax. The syntax has lots of commands for lots of types of lines, but when blobbing with shape()
, weâll define curves using the curve
 command:
.blob {
clip-path: shape(
from X0 Y0,
curve to X1 Y1 with Xc1 Yc1,
curve to X2 Y2 with Xc21 Yc21 / Xc22 Yc22
/* ... */
);
}
Letâs break down each parameter:
X0 Y0
 defines the starting point of the shape.curve
 starts the curve whereÂX1 Y1
 is the next point of the shape, whileÂXc1 Yc1
 defines a control point used in BĂ©zier curves.- The next parameter is similar, but we usedÂ
Xc21 Yc21 / Xc22 Yc22
 instead to define two control points on the Bézier curve.
I honestly donât understand BĂ©zier curves and control points completely, but luckily, we donât need them to use shape()
 and blobs! Again, shape()
 uses a verbal version of SVGâs path
 syntax, so it can draw any shape an SVG can, which means that we can translate the SVG blobs we generated earlier⊠and CSS-ify them. To do so, weâll grab the d
 attribute (which defines the path
) from our SVG and paste it into Temaniâs SVG to shape()
 generator.
This is the exact code the tool generated for me:
.blob {
aspect-ratio: 0.925; /* Generated too! */
clip-path: shape(
from 91.52% 26.2%,
curve to 93.52% 78.28% with 101.76% 42.67%/103.09% 63.87%,
curve to 44.11% 99.97% with 83.95% 92.76%/63.47% 100.58%,
curve to 1.45% 78.42% with 24.74% 99.42%/6.42% 90.43%,
curve to 14.06% 35.46% with -3.45% 66.41%/4.93% 51.38%,
curve to 47.59% 0.33% with 23.18% 19.54%/33.13% 2.8%,
curve to 91.52% 26.2% with 62.14% -2.14%/81.28% 9.66%
);
}
As you might have guessed, it returns our beautiful blob:
Letâs check if it passes our requirements:
- Yes, they can be made of a single element.
- Yes, they can also be created in a generator and then translated into CSS.
- Yes, we can use gradient backgrounds, but due to the nature ofÂ
clip-path()
, borders and shadows get cut out.
Two out of three? Maybe two and a half of three? Thatâs a big improvement over the other approaches, even if itâs not perfect.
Conclusion
So, alas, we failed to find what I believe is the perfect CSS approach to blobs. I am, however, amazed how something so trivial designing blobs can teach us about so many tricks and new CSS features, many of which I didnât know myself.