Technique

Mesh gradients

Make a mesh gradient in CSS (stacked radial-gradients over a base colour), export it as SVG or PNG, and get the contrast report. Seeded Poisson-disc layout, so the same colours and seed give the same mesh forever. Free, no sign-up.

Open in the full maker →
Aa — the quick brown fox
black text · worst 5.35:1 · AA
banding high · 20.87px
4 stops · click the track to add · drag to move · select to edit

Export

CSS with the interpolation space, Tailwind v4, SVG with correct geometry, a DTCG token, and the PNG.

Apply this to a brand → sign in

.gradient {
  background-color: #9795ee;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='g' x='0' y='0' width='100%25' height='100%25' color-interpolation-filters='sRGB'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.858' numOctaves='2' seed='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3CfeComponentTransfer%3E%3CfeFuncR type='linear' slope='3' intercept='-1'/%3E%3CfeFuncG type='linear' slope='3' intercept='-1'/%3E%3CfeFuncB type='linear' slope='3' intercept='-1'/%3E%3C/feComponentTransfer%3E%3CfeColorMatrix type='matrix' values='1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 19 -12'/%3E%3CfeComponentTransfer%3E%3CfeFuncA type='linear' slope='0.012'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23g)'/%3E%3C/svg%3E"), radial-gradient(circle farthest-corner at 6.16% 12.62%, rgba(99, 91, 255, 0.72) 0%, rgba(99, 91, 255, 0) 50.06%), radial-gradient(circle farthest-corner at 95.51% 53.69%, rgba(144, 137, 252, 0.875) 0%, rgba(144, 137, 252, 0) 55.68%), radial-gradient(circle farthest-corner at 45.23% 15.03%, rgba(255, 128, 181, 0.728) 0%, rgba(255, 128, 181, 0) 49.83%), radial-gradient(circle farthest-corner at 37.97% 64.32%, rgba(0, 212, 255, 0.809) 0%, rgba(0, 212, 255, 0) 46.1%);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='g' x='0' y='0' width='100%25' height='100%25' color-interpolation-filters='sRGB'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.858' numOctaves='2' seed='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3CfeComponentTransfer%3E%3CfeFuncR type='linear' slope='3' intercept='-1'/%3E%3CfeFuncG type='linear' slope='3' intercept='-1'/%3E%3CfeFuncB type='linear' slope='3' intercept='-1'/%3E%3C/feComponentTransfer%3E%3CfeColorMatrix type='matrix' values='1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 19 -12'/%3E%3CfeComponentTransfer%3E%3CfeFuncA type='linear' slope='0.012'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23g)'/%3E%3C/svg%3E"), radial-gradient(circle farthest-corner at 6.16% 12.62%, oklch(from #635bff l c h / 0.72) 0%, rgb(from #635bff r g b / 0) 50.06%), radial-gradient(circle farthest-corner at 95.51% 53.69%, oklch(from #9089fc l c h / 0.8751) 0%, rgb(from #9089fc r g b / 0) 55.68%), radial-gradient(circle farthest-corner at 45.23% 15.03%, oklch(from #ff80b5 l c h / 0.7283) 0%, rgb(from #ff80b5 r g b / 0) 49.83%), radial-gradient(circle farthest-corner at 37.97% 64.32%, oklch(from #00d4ff l c h / 0.8087) 0%, rgb(from #00d4ff r g b / 0) 46.1%);
  background-size: 160px 160px, auto, auto, auto, auto;
  background-blend-mode: soft-light, normal, normal, normal, normal;
}

/* or keep the gradient untouched and overlay the grain: */
.gradient {
  position: relative;
  isolation: isolate;
}
.gradient::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='g' x='0' y='0' width='100%25' height='100%25' color-interpolation-filters='sRGB'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.858' numOctaves='2' seed='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3CfeComponentTransfer%3E%3CfeFuncR type='linear' slope='3' intercept='-1'/%3E%3CfeFuncG type='linear' slope='3' intercept='-1'/%3E%3CfeFuncB type='linear' slope='3' intercept='-1'/%3E%3C/feComponentTransfer%3E%3CfeColorMatrix type='matrix' values='1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 19 -12'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23g)'/%3E%3C/svg%3E");
  background-size: 160px 160px;
  image-rendering: pixelated;
  mix-blend-mode: soft-light;
  opacity: 0.0124;
}

How it works

There is no mesh gradient in CSS or in SVG 2 — the paint-servers chapter defines only linearGradient and radialGradient. Meshes survive as Coons patches in Illustrator and Inkscape and as a vendor feature in Figma, but nothing you can hand a browser. So "mesh" on the web is one of three techniques, and the generator above makes the first two.

1. Stacked radials — pure CSS, static, zero JS

Layer N radial-gradient()s with a transparent falloff over a base colour. Radial layers beat linear ones because the falloff is isotropic. The layout is the whole craft: we place the points with a seeded Poisson-disc sampler (minimum separation 0.35 of the box), give the highest-chroma colours to the points nearest the corners (corners carry the eye), and set each radius to about half the diagonal at 70–90% alpha. The seed is part of the spec, so the same colours and seed produce the same mesh forever — which is what makes it citable in a brand guide.

css · stacked radials
.mesh {
  background-color: #9795ee;
  background-image:
    radial-gradient(circle farthest-corner at 6.16% 12.62%, oklch(from #635bff l c h / 0.72) 0%, rgb(from #635bff r g b / 0) 50.06%),
    radial-gradient(circle farthest-corner at 95.51% 53.69%, oklch(from #9089fc l c h / 0.8751) 0%, rgb(from #9089fc r g b / 0) 55.68%),
    radial-gradient(circle farthest-corner at 45.23% 15.03%, oklch(from #ff80b5 l c h / 0.7283) 0%, rgb(from #ff80b5 r g b / 0) 49.83%),
    radial-gradient(circle farthest-corner at 37.97% 64.32%, oklch(from #00d4ff l c h / 0.8087) 0%, rgb(from #00d4ff r g b / 0) 46.1%);
}

2. SVG — the export that matters for a brand kit

The same layout baked as nested <radialGradient> + <rect> elements: portable, printable, resolution-independent, and it imports into Figma. The PNG is rendered from this SVG, so all three agree.

3. WebGL — animated

Stripe-style motion is a fullscreen quad with per-fragment simplex-noise weights over the palette colours. The one change that separates a shader that looks like Stripe from one that looks like a lava lamp is blending in OKLab inside the shader. We plan to ship this on @paper-design/shaders-react (Apache-2.0) once the dependency lands — it is deferred, not forgotten.

The gotcha: layers composite in sRGB

Each layer can say in oklch, but the layers blend in sRGB because ordinary compositing is not colour-space-aware. So a CSS mesh is inherently a little less clean than a shader. Two mitigations, both built in: keep adjacent blobs close in hue, and add a few percent of grain (the slider above; see grainy gradients).

Starting point, as code

css
.gradient {
  background-color: #9795ee;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='g' x='0' y='0' width='100%25' height='100%25' color-interpolation-filters='sRGB'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.858' numOctaves='2' seed='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3CfeComponentTransfer%3E%3CfeFuncR type='linear' slope='3' intercept='-1'/%3E%3CfeFuncG type='linear' slope='3' intercept='-1'/%3E%3CfeFuncB type='linear' slope='3' intercept='-1'/%3E%3C/feComponentTransfer%3E%3CfeColorMatrix type='matrix' values='1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 19 -12'/%3E%3CfeComponentTransfer%3E%3CfeFuncA type='linear' slope='0.012'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23g)'/%3E%3C/svg%3E"), radial-gradient(circle farthest-corner at 6.16% 12.62%, rgba(99, 91, 255, 0.72) 0%, rgba(99, 91, 255, 0) 50.06%), radial-gradient(circle farthest-corner at 95.51% 53.69%, rgba(144, 137, 252, 0.875) 0%, rgba(144, 137, 252, 0) 55.68%), radial-gradient(circle farthest-corner at 45.23% 15.03%, rgba(255, 128, 181, 0.728) 0%, rgba(255, 128, 181, 0) 49.83%), radial-gradient(circle farthest-corner at 37.97% 64.32%, rgba(0, 212, 255, 0.809) 0%, rgba(0, 212, 255, 0) 46.1%);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='g' x='0' y='0' width='100%25' height='100%25' color-interpolation-filters='sRGB'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.858' numOctaves='2' seed='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3CfeComponentTransfer%3E%3CfeFuncR type='linear' slope='3' intercept='-1'/%3E%3CfeFuncG type='linear' slope='3' intercept='-1'/%3E%3CfeFuncB type='linear' slope='3' intercept='-1'/%3E%3C/feComponentTransfer%3E%3CfeColorMatrix type='matrix' values='1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 19 -12'/%3E%3CfeComponentTransfer%3E%3CfeFuncA type='linear' slope='0.012'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23g)'/%3E%3C/svg%3E"), radial-gradient(circle farthest-corner at 6.16% 12.62%, oklch(from #635bff l c h / 0.72) 0%, rgb(from #635bff r g b / 0) 50.06%), radial-gradient(circle farthest-corner at 95.51% 53.69%, oklch(from #9089fc l c h / 0.8751) 0%, rgb(from #9089fc r g b / 0) 55.68%), radial-gradient(circle farthest-corner at 45.23% 15.03%, oklch(from #ff80b5 l c h / 0.7283) 0%, rgb(from #ff80b5 r g b / 0) 49.83%), radial-gradient(circle farthest-corner at 37.97% 64.32%, oklch(from #00d4ff l c h / 0.8087) 0%, rgb(from #00d4ff r g b / 0) 46.1%);
  background-size: 160px 160px, auto, auto, auto, auto;
  background-blend-mode: soft-light, normal, normal, normal, normal;
}
tailwind v4
bg-[radial-gradient(circle_farthest-corner_at_6.16%_12.62%,oklch(from_#635bff_l_c_h_/_0.72)_0%,rgb(from_#635bff_r_g_b_/_0)_50.06%),radial-gradient(circle_farthest-corner_at_95.51%_53.69%,oklch(from_#9089fc_l_c_h_/_0.8751)_0%,rgb(from_#9089fc_r_g_b_/_0)_55.68%),radial-gradient(circle_farthest-corner_at_45.23%_15.03%,oklch(from_#ff80b5_l_c_h_/_0.7283)_0%,rgb(from_#ff80b5_r_g_b_/_0)_49.83%),radial-gradient(circle_farthest-corner_at_37.97%_64.32%,oklch(from_#00d4ff_l_c_h_/_0.8087)_0%,rgb(from_#00d4ff_r_g_b_/_0)_46.1%)]

Questions

Can I export a mesh gradient as SVG?
Yes. The layout is baked as nested radialGradient + rect elements in SVG 2; the PNG is rendered from that same SVG, so CSS, SVG and PNG agree.
Can I use a mesh gradient on my website?
Yes — as stacked CSS radial-gradients over a base colour (no JavaScript), or as the SVG/PNG. The layers composite in sRGB, so keep adjacent blobs close in hue and add a few percent of grain.
Why does the same mesh come back every time?
The layout is a seeded Poisson-disc sample: the seed is part of the spec, so the same colours and seed give the same mesh forever. Press Re-lay for a new seed.

More

mcpConnect your agent to the gradient engine
live endpoint

Point any MCP client at the shared gradients endpoint and your agent gets the same engine as this page — make_gradient returns CSS with the interpolation space, Tailwind v4, SVG, a PNG URL, a DTCG token and the contrast report in one call; brand_gradients derives the whole contact sheet from a brand or a palette. Read-only, no key. Every gradient is a /gradients/new/… link: open it, refine, paste it back.

endpointhttps://mcp.brandsweets.com/gradients/mcp
claude mcp add --transport http brandsweets-gradients https://mcp.brandsweets.com/gradients/mcp
try this prompt

make a mesh gradient from #635bff, #00d4ff and #ff80b5 with seed 7, export the SVG, and tell me if white text passes AA

Want the agent to remember your picks and write them into a living brand? Create a free BrandSweets workspace.