Mesh Gradient Generator

Soft overlapping radial gradients that read like a mesh gradient, previewed at hero size with text on them, and reproducible from a seed.

Enable JavaScript to change the gradient; the CSS below is for the defaults.

Layout

The last colour given is also the base, so the gaps between the blobs read as part of the gradient rather than as holes.

How far each gradient reaches. Large is the point: a blob smaller than the element shows its edge, which reads as a circle rather than as a mesh.

The positions come from this, so the same seed is the same gradient tomorrow and in the published page.

A hero, with words on it

Large on purpose: a mesh gradient reads completely differently at the size it will actually be used, and the text is here because something will be.

mesh.css
.hero {
	background-color: rgb(245 158 11);
	background-image: radial-gradient(at 1% 11.7%, rgb(30 86 236 / 0.9) 0px, transparent 70%), radial-gradient(at 84.8% 10.3%, rgb(124 58 237 / 0.9) 0px, transparent 70%), radial-gradient(at 20% 81.9%, rgb(226 69 107 / 0.9) 0px, transparent 70%), radial-gradient(at 86.9% 95.1%, rgb(245 158 11 / 0.9) 0px, transparent 70%);
}

/* Seed 7: the positions come from it, so this
   gradient is reproducible rather than a lucky refresh. */

A mesh gradient in a design tool is a warped grid of colour, and CSS has no equivalent. What CSS does have is several large, soft radial gradients over a base colour, which reads as the same thing at the sizes people actually use it: a hero, a card, a section background.

Two details separate that from a muddy blur. The gradients have to be much larger than the element, so no edge of a circle is ever visible. And the base colour has to be one of the mesh colours rather than white, or the gaps between the blobs read as holes.

The positions come from a seed rather than from randomness, which means the same seed is the same gradient tomorrow, in the published page, and in the server-rendered first paint. “New seed” changes the seed rather than the output directly, so what you pick is reproducible instead of being a lucky refresh.

How to use

  1. Pick a layout and four or five colours. The last one is also the base.
  2. Set the blob size high enough that you cannot see any circle edges. 70 percent is a sensible start.
  3. Press “New seed” until the arrangement is right, then note the seed number that comes with the CSS.

Example

.hero {
	background-color: rgb(245 158 11);
	background-image: radial-gradient(at 1% 11.7%, rgb(30 86 236 / 0.9) 0px, transparent 70%), radial-gradient(at 84.8% 10.3%, rgb(124 58 237 / 0.9) 0px, transparent 70%), radial-gradient(at 20% 81.9%, rgb(226 69 107 / 0.9) 0px, transparent 70%), radial-gradient(at 86.9% 95.1%, rgb(245 158 11 / 0.9) 0px, transparent 70%);
}

/* Seed 7: the positions come from it, so this
   gradient is reproducible rather than a lucky refresh. */

Four gradients, one base colour, no image. The preview is deliberately large, because a mesh gradient at thumbnail size tells you nothing about how it will look behind a headline.

Pitfalls

Small blobs show their edges. Below about 40 percent you are looking at circles rather than at a mesh. Larger and softer nearly always reads better.

White gaps read as holes. That is why the base is the last colour rather than the page background. If the gradient looks patchy, the base is wrong.

Text on top needs checking. A gradient has a lightest point and a darkest point, and a contrast checker can only test one colour at a time. Check the text against both extremes.

Too many colours turns to mud. Three or four is usually the limit before the blobs average out into grey in the middle. Five works when they are close in hue.

Keep the seed. It is the only thing that makes a gradient you liked reproducible. It is written into the CSS as a comment for that reason.

It is not a mesh gradient. A real one warps a grid; this approximates the look with radial gradients. For anything that has to match a design file exactly, export an image.

Large blurred areas cost paint. Not much, and not nothing. On a page with several of them on a phone it is worth checking.

Compatibility

radial-gradient with an at position and multiple background images works in every browser in use, so the output needs no fallback.

The positions come from a small integer sequence seeded by the number you choose, and that sequence is implemented identically in JavaScript and in PHP. The mirror comparison runs on every build, which is what guarantees the server-rendered first paint is the same gradient rather than a similar one that shifts when the page hydrates.

The tests pin the properties that matter: the same seed gives the same positions, different seeds give different ones, every blob stays within ten percent of the element’s bounds, and the base colour is one of the mesh colours rather than white.

Colours run through the same helper as the shadow tools, so a solid colour prints as rgb() with no alpha and a partly transparent one prints with a slash alpha.

Frequently asked questions

Why does my gradient look grey in the middle?
Too many colours overlapping, or opacity too low. Fewer colours, or a higher opacity with a larger spread, keeps them distinct.
How do I get the same gradient again?
Use the same seed. It is in the CSS comment, and it is the whole reason the positions are seeded rather than random.
Can I animate it?
Not the positions, which are static in the output. For movement, the animated background generator does the composited version with drifting blobs.
Will it work as a full-page background?
Yes, on body, and it costs a little paint on a large viewport. Check on a phone before shipping it behind a long page.
How do I make it darker for dark mode?
Put the colours in custom properties and set them per theme. The gradient structure stays; only the colours change.
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.