Blob, Blob, Blob: Four Ways to Make Organic Shapes in CSS

Designing blobs in CSS is a surprisingly deep rabbit hole. What seems like a simple visual request—an organic, irregular shape—can be solved in several completely different ways, each with its own constraints. To judge which approach is actually worth using, we can measure them against three reasonable requirements:

  1. The shape comes from a single element, ideally without pseudo-elements.
  2. It is designable through an online tool or generator.
  3. We can apply gradients, borders, shadows, or other CSS effects without breaking the illusion.

Skip CSS entirely

Because blobs follow no strict geometry, the most common production method is to generate them outside of CSS as SVGs. Dedicated tools like Haikei and Blobmaker output organic shapes in seconds. Any vector editor also works and can export the final result as an SVG. For instance, generating a blob in Haikei gives you something like the following that you can drop straight into your markup:

Randomly shaped blob in bright red.
<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>

This passes the design-test requirement easily, but counts against the single-element rule since you must inline or reference an <svg>.

The classic border-radius hack

Years before CSS had native path-based clipping, developers discovered that border-radius could fake a blob. It is not intuitive at first glance. The trick is that each corner property is actually a shorthand for horizontal and vertical radii. Normally these are equal, giving a circular corner, but they don't have to be.

Setting a grid of four border-radius values with equal horizontal and vertical radii for each corner yields a bubbly square:

<div class="blob"></div>
.blob {
  border-radius: 25% 50% 75% 100%;
}

The corners follow a circle of the given radius. A value such as 25% means the top-left corner traces a circle whose radius is 25% of the element's size:

.blob {
  border-top-left-radius: 25%;
}

To get an elliptical corner, you pass pairs of values with a forward slash (/) to separate horizontal from vertical radii. For example, border-radius: 25% / 50% uses 25% of the element's width as the horizontal radius and 50% of its height as the vertical radius:

.blob {
  border-top-left-radius: 25% 50%;
}
.blob {
  border-radius:
    /* horizontal */
    100% 30% 60% 70% /
    /* vertical */
    50% 40% 70% 70%;
}

The syntax is cumbersome for hand-authoring, but Nils Binder built a visual tool precisely for this effect, so designers can prototype and export CSS directly.

That wins on usability, but the effect has hard limits. The shape is always convex. Combine multiple border-radius blobs to get concave features, but at a cost:

  1. More elements and pseudo-elements are needed.
  2. No tool exists to prototype the output of fused elements, leaving you to fiddle manually.
  3. Borders, gradients, and shadows expose edges that are otherwise invisible.
Concave and convex shapes

The technique is clever but brittle. Using it purely for a complex blob—especially for animation—collapses under its own weight.

SVG filters and multiple backgrounds

A different approach uses the classic "gooey" effect. Multiple circles, either separate elements or background layers, connect when overlap regions sustain both increased blur and increased contrast.

For a single CSS-based version, the original demo relied on an HTML structure of several overlapping elements inside one parent container:

<div class="blob">
  <div class="subblob"></div>
  <div class="subblob"></div>
  <div class="subblob"></div>
</div>

Then blur each individual shape and apply contrast only to the parent:

.blob {
  filter: contrast(50);
  background: white; /* Solid colors are necessary */
}

.subblob {
  filter: blur(15px);
  background: black; /* Solid colors are necessary */
}

The trouble is that regular CSS contrast() applies to every color channel at once. That changes hues as soon as the source shapes are not pure black or white.

Instead, we can delegate to SVG's filter system, which lets us apply contrast with a higher degree of control. By only boosting the contrast of the alpha channel, colors stay exact while the shapes still meld:

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

The filter can be wired through the standard CSS property simply by naming it:

.blob {
  filter: url("#blob");
}
<div class="blob"></div>

The entire wizardry runs on a <div class="blob"></div> that creates circles out of nothing but backgrounds. You can place a fixed-size gradient in the background and tune it using background-size and background-position. Since the syntax accepts layers, you can stack an arbitrary number of circles:

body {
  background: radial-gradient(farthest-side, var(--blob-color) 100%, #0000);
  background-repeat: no-repeat; /* Important! */
}
body {
  background: radial-gradient(...) 20% 30% / 30% 40%;
  background-repeat: no-repeat; /* Important! */
}
.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;
}

The advantages are real: circles made of backgrounds are indistinguishable from shapes made of DOM elements to filters, gradients survive the approach, and a single element is enough per blob. The tradeoff is less ergonomic design-time: you still need a generator-like mindset to place circles correctly—any error shows up immediately and is hard to fine-tune.

The modern clip-path approach with shape()

Walking through all that history sets up the most current trick: clip-path: shape(). shape() is a new CSS function introduced for polygon-like clipping that reads like a human-facing version of SVG's path syntax. To construct a blob-shaped clipping region, the function accepts a starting coordinate followed by one or more curve commands:

.blob {
  clip-path: shape(
    from X0 Y0, 
    curve to X1 Y1 with Xc1 Yc1, 
    curve to X2 Y2 with Xc21 Yc21 / Xc22 Yc22
    /* ... */
  );
}

Each curve block covers Bézier controls points and end coordinates, but unlike drawing circles blindly, you can steal your blob's geometry from existing SVG generators. Grab the d attribute from your SVG and paste it into Temani Afif's SVG-to-shape() generator. The converter spits out a compact, working clip-path rule such as the one produced for Haikei's blob:

.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%
  );
}

Simply apply that clipped element with your background using any property that accepts colors—notably gradients:

  • The shape requires only one element and no pseudo-elements.
  • An online SVG generator plus the converter covers design and translation.
  • Gradients work inside the clipped area, but borders and drop shadows disappear because clip-path guts anything outside the boundary.

No single approach nails all three criteria completely. The border-radius method struggles with versatility, SVG blobs are beautiful but require external assets, the gooey-effect trick is powerful yet hard to hand-tune, and shape() clips around edges we may want to keep. Still, each path teaches something different about how CSS handles curves—and there is now a respectable toolbox for making blobs without stepping outside CSS when that matters.