Those gooey shapes drifting behind hero text on half the SaaS sites you visit? They look like they took a design tool and a plugin and an afternoon. They took one CSS property and a keyframe. The whole thing is border-radius changing its mind, slowly, on a loop.
Blob animation css animates the border-radius property between organic shapes so a plain box turns into a soft, wandering blob. No SVG, no canvas, no library. This guide starts with the single element, decodes the eight-value radius syntax that makes the shape look hand-drawn, builds the morph, layers a few blobs into a hero background, and closes with the performance and accessibility parts that keep it from wrecking your page. It builds on the core CSS animation model, so if @keyframes or animation-direction are new, start there.
Blob animation css is just border-radius
Let me talk you out of a mistake first. The JavaScript blob generators, the ones that ship a kilobyte of code to compute an SVG path, are overrated for this job. If the blob is decorative background and does not need to react to data, you do not need script at all. You need a div and a border radius that is not the same on every corner.
Here is the resting shape. One element, one background, one radius that leans.
.blob {
width: 320px;
height: 320px;
background: #6366f1;
border-radius: 42% 58% 70% 30% / 45% 45% 55% 55%;
}
That is already a blob. Not a circle, not a rounded square, a lopsided organic shape that reads as friendly. The magic is entirely in those eight numbers and the slash in the middle, which is where most people’s understanding falls off a cliff. So let us fix that, because once the syntax makes sense you can design any blob you want by feel instead of by copy-paste.
The eight-value radius syntax
Read the syntax once, slowly, and it stops being scary. border-radius can take up to eight values split by a slash. The four values before the slash are the horizontal radii. The four after are the vertical radii. Both sets run in the same order: top-left, top-right, bottom-right, bottom-left, going clockwise from the top-left corner.
border-radius:
42% 58% 70% 30% /* horizontal: TL TR BR BL */
/ 45% 45% 55% 55% ; /* vertical: TL TR BR BL */
The slash is the whole trick. It separates horizontal from vertical, and that is what lets a single corner be wide but shallow, or narrow but deep. When a corner’s horizontal and vertical radii match, you get a plain circular curve. When they differ, the corner turns elliptical, a stretched curve, and a shape built from four different ellipses is exactly what an organic blob is.
Keep all eight values as percentages, not pixels. Percentages scale the curve to the element, so the same radius makes a coherent blob at 200px or 500px. Pixels lock the curve to an absolute size and the shape falls apart the moment the box changes. A quick sanity rule while you experiment: opposite corners often want to roughly sum toward 100%, which keeps the blob feeling balanced rather than pinched. Break that rule on purpose once you know what balanced feels like.
Building the morph keyframes
A static blob is nice. A blob that slowly breathes between two shapes is the effect people actually want. And you only need to author two shapes, because animation-direction: alternate will play them forward, then backward, forever, so the blob eases out to a second form and drifts home without you writing the return trip.
@keyframes morph {
from { border-radius: 42% 58% 70% 30% / 45% 45% 55% 55%; }
to { border-radius: 70% 30% 46% 54% / 30% 60% 40% 70%; }
}
.blob {
animation: morph 8s ease-in-out infinite alternate;
}
Two things carry this. First, alternate, which means the loop reverses on every other cycle, so the motion is continuous instead of snapping back to the start. Second, ease-in-out, and a slow duration. A blob on linear looks like a machine part. On a lazy ease-in-out over eight seconds it looks like it is floating in liquid, drifting and settling. That pairing, a slow curve with a long duration, is what separates a calm background from a nervous one. The timing function guide goes deeper on why ease-in-out reads as organic.
Want more life? Stack a second animation on the same element. A morph for the shape, a painfully slow rotate for drift, running independently.
.blob {
animation:
morph 8s ease-in-out infinite alternate,
spin 20s linear infinite; /* transform: cheap to run */
}
@keyframes spin {
to { transform: rotate(360deg); }
}
Two animations, one element, comma between them. The rotate uses transform, which barely costs anything, so it is a near-free way to hide the fact that the morph only has two keyframes. The eye reads a shape that is always changing.
Layering blobs for a hero background
One blob is a shape. Three overlapping blurred blobs in different colors is that dreamy gradient-mesh look every landing page is chasing. The recipe: position them absolutely inside the hero, blur them hard, give each its own color and its own timing so they never sync, and float your real content above on a higher z-index.
.hero {
position: relative;
overflow: hidden; /* clip blobs to the hero */
isolation: isolate; /* keep blend modes contained */
}
.hero .blob {
position: absolute;
width: 40vw;
height: 40vw;
filter: blur(60px); /* the soft, glowy edge */
opacity: 0.55;
}
.blob-1 { top: -8%; left: -6%; background: #8b5cf6;
animation: morph 9s ease-in-out infinite alternate; }
.blob-2 { top: 20%; right: -8%; background: #ec4899;
animation: morph 11s ease-in-out infinite alternate;
animation-delay: -4s; } /* start mid-cycle, out of phase */
.blob-3 { bottom: -12%; left: 30%; background: #38bdf8;
animation: morph 13s ease-in-out infinite alternate;
animation-delay: -7s; }
.hero-content {
position: relative;
z-index: 1; /* text sits above the blobs */
}
The negative animation-delay is the detail that makes it look designed. A negative delay starts an animation partway through its cycle, so blob two and blob three begin already mid-morph and out of step with blob one. Without it, three blobs pulse in lockstep and the illusion collapses into an obvious loop. Different durations plus negative delays, and no two frames ever repeat.
Mind the text on top. A blurry, moving, saturated background is where accessibility quietly dies, because contrast against a shifting surface is a moving target. Keep the blobs low in opacity, or drop a semi-opaque panel behind the headline, and check the contrast at the blob’s brightest, not its average. Pretty is not worth unreadable.
Performance and reduced motion
Time for the honest part. Animating border-radius is not free the way animating transform is. Radius changes happen on the paint step, so the browser repaints the blob every single frame. That is cheaper than a layout reflow, but it is real work, and you have wrapped each blob in a blur(60px) filter, which is one of the more expensive things you can ask a GPU to do continuously. Three big blurred, repainting blobs is where a buttery hero turns into a laptop fan.
So set a budget and stay in it. A few practical limits that hold up in the wild:
- Two or three blobs, not eight. Each blurred, morphing element is a recurring paint cost.
- Keep the blur reasonable.
blur(60px)on a huge element is heavy; test on a mid-range phone, not your desktop. - Let the near-free
transform: rotatedo the visible work and keep the paint-heavy morph subtle, so you get motion without paying for it three times over. - Reach for
will-change: transformonly if you measure a real gain, and only on the rotate. Slappingwill-changeon everything hands the compositor a memory bill for nothing.
The deeper mechanics of why paint costs more than composite, and how to watch for dropped frames in devtools, are laid out in the CSS animation performance guide. For a background blob it is worth a read, because this is precisely the kind of always-on animation that eats a frame budget you did not know you had.
Then there is motion preference, and a blob is the easy case. It is pure decoration, it carries no information, so when someone asks for reduced motion you simply stop it. Freeze the shape mid-drift and leave a perfectly good static blob behind.
@media (prefers-reduced-motion: reduce) {
.blob,
.hero .blob {
animation: none; /* stop morphing and spinning */
}
}
Nothing is lost. The blobs keep their organic border radius, the colors and blur stay, and the hero still looks like the hero. The only thing that leaves is the movement that a subset of people find genuinely unpleasant. That is the whole deal with decorative motion: it should be the first thing to go and the last thing anyone misses. The broader pattern for cutting motion without gutting a design is in accessible animation with prefers-reduced-motion.
Build the blob, keep it slow, keep it cheap, and let people turn it off. Do those four and you have the effect that looks like a plugin, running on a div.