SVG Blob Shape Generator

Smooth random blobs that fit their viewBox exactly, because the bounds are solved rather than guessed, with the SVG, a CSS background and a clip-path.

Live output

Enable JavaScript to customise; default output below.

How many lobes the shape can have. Three to five is bold, six to nine is the usual decorative range, and past a dozen it reads as a circle with a rough edge.

How far each point may move in or out. At 0 it is a circle.

The coordinate space, not the rendered size. An SVG scales to whatever CSS says.

Fill

"none" fills with currentColor, so the shape takes the colour of the text around it.

Any CSS colour. A hash is encoded as %23 in the data URI version, which is the usual reason an inline SVG background fails.

Live preview blob.svg.txt
Points               6
Wobble               45% of the radius
viewBox              0 0 500 500
Fill                 gradient
Path length          290 characters

SVG
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 500" width="500" height="500" role="img" aria-hidden="true">
	<defs>
		<linearGradient id="blob-6-45" x1="14.64%" y1="14.64%" x2="85.36%" y2="85.36%">
			<stop offset="0%" stop-color="#1e56ec" />
			<stop offset="100%" stop-color="#7c3aed" />
		</linearGradient>
	</defs>
	<path d="M484.287,239.699C485.915,364.235 476.396,335.044 340.957,416.582C205.518,498.12 223.425,529.872 108.067,466.194C-7.292,402.516 15.994,394.942 20.516,239.699C25.039,84.456 6.899,95.821 120.63,34.964C234.36,-25.894 205.518,-3.055 336.434,70.65C467.351,144.354 482.659,115.163 484.287,239.699Z" fill="url(#blob-6-45)" />
</svg>

As a CSS background
.hero::before {
	background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 500 500' width='500' height='500' role='img' aria-hidden='true'%3E %3Cdefs%3E %3ClinearGradient id='blob-6-45' x1='14.64%25' y1='14.64%25' x2='85.36%25' y2='85.36%25'%3E %3Cstop offset='0%25' stop-color='%231e56ec' /%3E %3Cstop offset='100%25' stop-color='%237c3aed' /%3E %3C/linearGradient%3E %3C/defs%3E %3Cpath d='M484.287,239.699C485.915,364.235 476.396,335.044 340.957,416.582C205.518,498.12 223.425,529.872 108.067,466.194C-7.292,402.516 15.994,394.942 20.516,239.699C25.039,84.456 6.899,95.821 120.63,34.964C234.36,-25.894 205.518,-3.055 336.434,70.65C467.351,144.354 482.659,115.163 484.287,239.699Z' fill='url(%23blob-6-45)' /%3E %3C/svg%3E");
	background-repeat: no-repeat;
	background-size: contain;
}

As a clip path
.avatar {
	clip-path: path("M484.287,239.699C485.915,364.235 476.396,335.044 340.957,416.582C205.518,498.12 223.425,529.872 108.067,466.194C-7.292,402.516 15.994,394.942 20.516,239.699C25.039,84.456 6.899,95.821 120.63,34.964C234.36,-25.894 205.518,-3.055 336.434,70.65C467.351,144.354 482.659,115.163 484.287,239.699Z");
	width: 500px;
	height: 500px;
}

This is the fixed shape used when there is no randomness available,
which is what the build sees. In a browser the page draws a fresh blob
on every load.

The shape is measured and then fitted, so it touches two edges of the
viewBox exactly and never clips. A Bézier curve bulges past the points
it joins, so a blob sized from its ring alone overflows its own box
about a third of the time, usually at the top or the left where it is
least obvious.

Fewer points make a bolder shape: 6 is the usual range for a decorative
blob.

The background version is a percent-encoded data URI rather than base64:
it is smaller, it stays readable in the stylesheet, and a colour can be
edited in place. Note that a hash in a colour has to be encoded as %23
or the URL ends there, which is the single most common reason an inline
SVG background silently fails.

clip-path: path() takes the path in pixels rather than as a percentage,
so it does not scale with the element. For a shape that scales, use
clipPathUnits="objectBoundingBox" in an SVG clipPath, or keep the blob
as a background image.

aria-hidden is on the SVG because a decorative shape should not be
announced. If the blob carries meaning, remove it and add a title
element instead.

Output is valid and updates as you type.

A blob is a ring of points at even angles, each pushed in or out at random, joined with curves. Two details separate one that looks organic from one that looks like a polygon with rounded corners.

The joins. Each point’s control handles follow the direction of the line between its neighbours, so the curve leaves and arrives along the same tangent and the join is invisible. Joining the points with quadratic curves through midpoints, the usual shortcut, leaves flat spots wherever the radius changes quickly.

The fit. A Bézier curve bulges past the points it joins, so a blob sized from its ring overflows its own viewBox about a third of the time, usually at the top or the left where nobody is looking. Here the bounds are solved, not guessed, and the shape is scaled into the box afterwards, so every blob touches two edges exactly and never clips.

Shapes are drawn fresh in your browser on each load.

How to use

  1. Set the number of points. Three to five is bold, six to nine is the usual decorative range.
  2. Set the wobble. At zero it is a circle.
  3. Pick a fill, or choose none to inherit currentColor.
  4. Copy the SVG, the CSS background, or the clip path.

Example

Six points at 45 percent wobble, with a gradient:

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 500" width="500" height="500" role="img" aria-hidden="true">
	<defs>
		<linearGradient id="blob-6-45" x1="14.64%" y1="14.64%" x2="85.36%" y2="85.36%">
			<stop offset="0%" stop-color="#1e56ec" />
			<stop offset="100%" stop-color="#7c3aed" />
		</linearGradient>
	</defs>
	<path d="M484.287,239.699C485.915,364.235 476.396,335.044 340.957,416.582…Z" fill="url(#blob-6-45)" />
</svg>

Yours will differ. Note the aria-hidden: a decorative shape should not be announced, and an unlabelled SVG with role="img" is announced as an image with no name.

Pitfalls

A blob sized from its ring clips. That is the bug this tool exists to avoid, and it is worth knowing if you write your own: the extremes of a cubic are at its endpoints and wherever its derivative is zero, which is a quadratic and therefore solvable. Sampling the curve at twenty points misses the peak between them.

%23 for the hash. In a data URI inside url(), a # starts a fragment, so fill="#1e56ec" truncates the SVG at the colour and the background silently does not appear. This is the single most common reason an inline SVG background fails, and the generated URI encodes it.

Percent-encode rather than base64. The result is smaller, it stays readable in the stylesheet, and you can edit a colour in place. Base64 for an SVG is a habit from the days of worse tooling.

clip-path: path() does not scale. The path is in pixels, so clipping an element of a different size crops or leaves gaps. For a shape that scales with its element, use an SVG clipPath with clipPathUnits="objectBoundingBox", or keep the blob as a background image with background-size: contain.

Ids have to be unique on the page. Two gradients called blob-gradient in one document and the second one wins for both shapes. The generated id includes the settings, which helps and does not guarantee it.

More points is not more organic. Past about twelve, the wobbles start cancelling each other out and the shape reads as a circle with a rough edge. Fewer, larger lobes look deliberate.

Decorative means decorative. If the blob carries meaning, remove aria-hidden and add a <title> inside the SVG. If it does not, leave it hidden and do not give it alt text.

Compatibility

Randomness comes from crypto.getRandomValues, and the page recomputes on load, so what you see is yours. At build time there is no randomness, so the compiled default output is a fixed shape with a note saying so, rather than a blob baked into a page every visitor would share.

The geometry was checked two ways: the solved bounds are compared against dense sampling of the curves in the test suite, and the rendered shapes were measured with the browser’s own getBBox, which agreed and reported no overflow across a spread of point counts and wobbles. Before the fit was added, four of twelve blobs overflowed their viewBox by up to eight units.

SVG and clip-path: path() are in every current browser; path() arrived in Chrome and Edge in 2019, Firefox in 2020 and Safari in 2021. The data URI background works everywhere. The gradient angle follows the CSS convention, degrees clockwise from the top, so 135 matches linear-gradient(135deg, …).

The path is rounded to three decimals, which is past what any renderer can resolve and keeps the string short. Running the output through the SVG optimiser on this site will shave a little more.

Frequently asked questions

How do I get the same blob again?
Save the SVG. There is deliberately no seed field: a seeded generator invites people to store the seed rather than the shape, and the shape is the thing you actually need.
Can I use one as a section divider?
Yes, though a divider usually wants a shape that is flat on one edge rather than a closed blob. The SVG wave generator on this site is the right tool for that.
Why is my background not showing?
Check the # in the colour. If it is not encoded as %23, the URL ends at the colour and the browser has nothing to draw. Then check that the element has a size: a ::before with no dimensions has nothing to paint into.
Should I inline the SVG or use a background?
Inline if you want to animate or recolour parts of it with CSS. A background if it is purely decorative, since it keeps the markup clean and the shape out of the accessibility tree without needing aria-hidden.
Can I animate between two blobs?
With the same number of points, yes: the paths have the same structure, so a CSS or JavaScript tween between the two d values works. With different point counts it does not, because the curves do not correspond.
Weekly drops

New tools, when there are new tools

One email when something worth using ships. No schedule to fill, so no filler.

Your address goes nowhere else, and one click unsubscribes.