How to Create a CSS Mesh Gradient (With Working Code, No Generator Required)
Build a CSS mesh gradient by hand with working code, avoid muddy color overlaps, animate it without killing performance, and use it with Tailwind CSS v4.

Mesh gradients are everywhere in modern SaaS design — soft, organic, multi-colored backgrounds that look like they were painted rather than coded. Here's the part most tutorials skip: CSS has no mesh-gradient() function. There is no native property for this. What you're seeing is an illusion built entirely from stacked radial-gradient() layers.
This guide shows you the actual working code, why your colors might look muddy where they overlap, how to animate it without tanking your page's frame rate, and how it actually works in Tailwind CSS v4.
What a CSS Mesh Gradient Actually Is
Since there's no dedicated mesh gradient property, the effect is simulated by layering several semi-transparent radial-gradient() values in a single background-image declaration, each positioned at a different coordinate and fading to transparent. Where the layers overlap, the colors blend — that blending is the entire trick.
The Exact CSS to Build One By Hand
Here is the clean CSS code to build a static 4-layer mesh gradient:
.mesh-gradient {
background-color: #ffffff;
background-image:
radial-gradient(circle at 20% 30%, #609eff 0%, transparent 50%),
radial-gradient(circle at 80% 20%, #fcb055 0%, transparent 50%),
radial-gradient(circle at 40% 80%, #ff6ec7 0%, transparent 50%),
radial-gradient(circle at 90% 90%, #7c4dff 0%, transparent 50%);
}
Each line is one "blob" — a color, a position (at X% Y%), and a transparent falloff point. Stack enough of them with soft edges and they blend together into that characteristic organic texture.
1–2 layers: Reads as a plain radial gradient, not a mesh.
3–4 layers: The sweet spot — enough depth and color variation without visual noise.
5–6 layers: Still fine, more complexity.
7+ layers: Overlaps start turning muddy, and each added layer costs paint performance without adding visible detail.
This runs on the browser's compositor and renders in every modern browser — no canvas, no image files, no JavaScript required for a static version.
Getting the Colors to Blend Cleanly (Not Muddy)
Two things commonly go wrong here:
Colors averaging toward gray: When semi-transparent layers of opposing hues overlap, standard alpha compositing can wash the blend toward neutral gray instead of a rich color mix. Fixing this usually means reducing how many layers directly overlap, raising the lightness of your colors, or setting background-blend-mode: screen on a dark base color.
Hard, banded edges instead of soft blending: Adding filter: blur(40–80px) on the element (or a wrapping pseudo-element) smooths the falloff between colors into something closer to what design tools like Figma produce natively.
One performance note on blur specifically: a static blurred mesh paints once and then just sits there — cheap. An animated blurred mesh repaints on every frame, which is a completely different performance story.
Animating a Mesh Gradient Without Killing Performance
This is where most implementations go wrong. The intuitive approach — writing @keyframes that shift the coordinates inside your radial-gradient() values over time — works, but it is expensive. Browsers treat gradients as images, not as compositable properties like transform or opacity. Animating the gradient itself forces a full repaint of the background on every single frame, which is exactly what causes scroll jank and dropped frames, especially on phones.
The standard fix used in production: don't animate the gradient itself — animate separate elements instead.
.mesh-blob-container {
position: absolute;
inset: 0;
filter: blur(80px);
overflow: hidden;
}
.blob {
position: absolute;
width: 40%;
height: 40%;
border-radius: 50%;
background: radial-gradient(circle, #609eff 0%, transparent 70%);
animation: drift 12s ease-in-out infinite alternate;
}
@keyframes drift {
from { transform: translate(0, 0); }
to { transform: translate(20%, 15%); }
}
Each color "blob" is its own element with a single simple gradient, animated purely with transform — which is GPU-composited and cheap regardless of how many blobs are moving. The blur on the parent container does the visual blending work, not the animation itself.
If you want tighter browser-native control, CSS Houdini's @property rule lets you register custom properties as animatable values, which can be interpolated directly inside a gradient. It is a genuinely elegant technique — but as of this writing, Firefox does not support @property, only Chrome, Edge, and Safari do. Use the blob-and-transform method above unless you are comfortable with a Firefox fallback.
One accessibility note that is easy to skip: 5–10% of users have prefers-reduced-motion enabled at the OS level, often for real vestibular or motion-sensitivity reasons. Respect it:
@media (prefers-reduced-motion: reduce) {
.blob {
animation: none;
}
}
Using Mesh Gradients With Tailwind CSS
Here is a distinction worth being precise about: Tailwind CSS v4 does have native bg-radial and bg-conic utilities, paired with from-, via-, and to- color stops. But those utilities generate exactly one gradient layer. A true mesh gradient needs multiple stacked layers, which Tailwind's built-in radial utility does not support on its own.
For a real multi-layer mesh, you need Tailwind's arbitrary value syntax — passing the full multi-layer CSS directly inside brackets, with spaces replaced by underscores:
<div class="bg-[radial-gradient(circle_at_20%_30%,#609eff_0%,transparent_50%),radial-gradient(circle_at_80%_20%,#fcb055_0%,transparent_50%),radial-gradient(circle_at_40%_80%,#ff6ec7_0%,transparent_50%)]"></div>
This works reliably, but arbitrary-value strings this long get unwieldy fast. For anything with more than 2–3 layers, most teams keep the gradient definition in a stylesheet or a Tailwind @theme custom property instead of inlining it directly in the class list.
Frequently Asked Questions
What is a CSS mesh gradient and how does it work?
It is an advanced visual texture created by layering multiple radial-gradient() values in a single background declaration, each positioned at different coordinates with semi-transparent falloffs. Where the individual gradients overlap, the colors blend, producing the soft, organic look — CSS has no dedicated mesh gradient function, so this layering is the entire technique.
Why do my gradient colors look muddy or gray where they overlap?
This usually happens when semi-transparent layers of opposing hues average toward neutral gray during compositing. Reducing the number of directly overlapping layers, increasing color lightness, or applying background-blend-mode: screen on a dark base typically fixes it.
Can I animate a mesh gradient without hurting performance?
Not by animating the gradient's coordinates directly — that forces a full repaint every frame. Instead, use separate blurred "blob" elements with simple single-color gradients, and animate their position with transform, which is GPU-composited and far cheaper.
Does Tailwind CSS support mesh gradients natively?
Tailwind v4's built-in bg-radial utility only generates a single gradient layer. A true multi-layer mesh gradient requires Tailwind's arbitrary bracket syntax with the full CSS gradient stack written inline, spaces replaced by underscores.
How many gradient layers should a mesh gradient have?
Three to four layers is the sweet spot for a rich, organic look without visual noise. Beyond six to eight layers, overlaps tend to turn muddy and each additional layer adds rendering cost without adding visible detail.
Skip the manual math on positions, falloffs, and color blending — build your mesh gradient visually and export the CSS or Tailwind code at Toolkito CSS Gradient Generator.
Toolkito Team
Contributing writer at Toolkito
More from the Blog & Tools

Tutorials
How to Remove Background from Images for Free Online
CSS Gradient Generator
Build linear, radial & mesh gradients with instant CSS & Tailwind code export.

Web Performance