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.
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.
Fix the highlighted fields to update the output.
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
- Set the number of points. Three to five is bold, six to nine is the usual decorative range.
- Set the wobble. At zero it is a circle.
- Pick a fill, or choose none to inherit
currentColor. - 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?
Can I use one as a section divider?
Why is my background not showing?
# 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?
aria-hidden.Can I animate between two blobs?
d values works. With different point counts it does not, because the
curves do not correspond.