The short version
- CSS has no native mesh gradient function, so a CSS mesh gradient is built by stacking several radial gradients over a flat base colour.
- Each layer is a pool of colour at its own position that fades to transparent, and the overlaps create the soft, multi-directional blend of a mesh.
- A blur filter on a pseudo-element softens the pools further, and a faint noise layer hides banding.
- Static layered gradients are cheap to render, but large blurs and animated layers repaint often and should be kept slow, small or optional.
- When a design needs many colour points, warped shapes or smooth motion at full screen, an exported image, video or WebGL shader is usually the better tool.
On this page
A CSS mesh gradient is a background where colour blends in several directions at once, as if pools of pigment had soaked into each other. CSS has no mesh function, and the mesh gradients once drafted for SVG never shipped in browsers, so the web version is a fake: three to five radial-gradient() layers stacked over a flat colour, each fading to transparent. Done carefully, it is close enough that nobody can tell, weighs a few hundred bytes and stays sharp at any size.
If you want the design theory first, what a mesh is and why it feels organic, read mesh gradients. This guide is about building one in code.
How the layering method works
Each layer is one colour point. Its position is the at value, its reach is the fade distance, and its strength is the colour's alpha. Because the first gradient in background-image is painted on top, order matters less than you might think here: every layer is mostly transparent, so they mix where they overlap. The base background-color shows through wherever no pool reaches.
.mesh {
background-color: #0b1026;
background-image:
radial-gradient(circle at 15% 20%, rgb(124 58 237 / 0.85), transparent 55%),
radial-gradient(circle at 85% 25%, rgb(236 72 153 / 0.75), transparent 50%),
radial-gradient(ellipse at 60% 90%, rgb(6 182 212 / 0.7), transparent 60%),
radial-gradient(circle at 25% 80%, rgb(245 158 11 / 0.55), transparent 45%);
}at value and the whole composition rebalances.Step by step: build your own CSS mesh gradient
- 1
Choose a ground
Pick the flat colour that shows between pools. Dark grounds make pools glow; light grounds make them feel like watercolour. The ground is also your fallback if the gradients fail.
- 2
Pick three to five related colours
Neighbouring hues blend cleanly. Opposites, such as blue and orange, meet in a grey smudge. Muddy gradients explains why and how to avoid it.
- 3
Place the pools off-centre
Push points towards corners and edges, and let at least one sit partly outside the box, such as
at 110% 0%. Centred pools look like a target. - 4
Vary reach and strength
Give one colour the largest fade and highest alpha so it leads. Keep the accent small and weaker. Equal pools look mechanical.
- 5
Mix in a better colour space
Add
in oklabto each layer, as inradial-gradient(in oklab circle at 15% 20%, ...), where supported. Overlaps stay brighter than in sRGB. - 6
Finish with grain
Large soft fades band on many screens. A faint noise overlay fixes it and adds a printed feel; see below.
Softer blends with blur
Radial fades are smooth but their shapes are obviously round. A blur softens the circles into something more organic. Put the layers on a pseudo-element, blur it, and oversize it with a negative inset so the blurred edges do not fade into the page. isolation: isolate keeps the negative z-index inside the component.
.mesh {
position: relative;
isolation: isolate;
overflow: hidden;
background-color: #0b1026;
}
.mesh::before {
content: "";
position: absolute;
inset: -15%;
z-index: -1;
background-image:
radial-gradient(circle at 20% 25%, #7c3aed, transparent 40%),
radial-gradient(circle at 80% 30%, #ec4899, transparent 35%),
radial-gradient(circle at 55% 85%, #06b6d4, transparent 40%);
filter: blur(48px);
}.mesh stays sharp because only the pseudo-element is blurred.Adding grain to a CSS mesh gradient
Grain does two jobs. It breaks up the visible steps that appear in wide, low-contrast fades, and it gives the surface the tactile, film-like quality that makes gradients look considered rather than default. In CSS, the usual source is an SVG feTurbulence filter used as a tiny background image, layered on top at low opacity. The full technique is in CSS noise texture, and the design reasoning in grainy gradients.
Without grain
- Visible rings in dark areas on many displays
- Surface reads as a flat screen colour
- Looks like a template default
With faint grain
- Steps are broken up and disappear
- Surface reads as paper, film or print
- Feels deliberate and finished
Animating a mesh gradient
Gradient positions are not animatable on their own, but registered custom properties are. Declare each moving coordinate with @property, use it inside the gradient, and animate the property. Keep the motion slow, and switch it off for people who ask for reduced motion.
@property --x1 {
syntax: "<percentage>";
inherits: false;
initial-value: 15%;
}
.mesh {
background-color: #0b1026;
background-image:
radial-gradient(circle at var(--x1) 20%, rgb(124 58 237 / 0.85), transparent 55%),
radial-gradient(ellipse at 60% 90%, rgb(6 182 212 / 0.7), transparent 60%);
animation: drift 20s ease-in-out infinite alternate;
}
@keyframes drift {
to { --x1: 55%; }
}
@media (prefers-reduced-motion: reduce) {
.mesh { animation: none; }
}Every frame of this animation repaints the whole background. On a small card that is trivial; on a full-screen hero it is worth measuring. The animated gradient guide covers cheaper alternatives such as moving an oversized layer with transform.
When to use an image or WebGL instead
| Approach | Best for | Watch out for |
|---|---|---|
| Layered CSS gradients | Static backgrounds, cards, heroes with three to five colours | Round-looking pools, banding without grain |
| CSS with blur | Softer, more organic static fields | Repaint cost if animated or resized often |
| Exported image (AVIF or WebP) | Complex meshes with many points or painterly shapes | File weight, and a fixed crop per format |
| Video loop | Rich motion on a hero | File size, autoplay rules, reduced motion |
| WebGL shader | Full-screen, fluid, interactive motion | Code complexity, battery use, fallbacks |
Shaders are covered in WebGL gradients. A middle path is to design the background once as a living material and export it: Gradiently's Marks are mesh-like materials with colour, grain, light and motion, and a design made with one exports as a PNG, a JPEG or a seamless 5 to 30 second video loop.

GR·Y97R·RQ
August Basin
See this MarkQuestions people ask
Can you make a mesh gradient with pure CSS?
Not natively, but stacking three to five radial gradients that fade to transparent over a base colour gives a convincing mesh. A blur and a noise layer finish it.
How many radial gradients does a CSS mesh need?
Three to five is usually enough. Fewer looks like a simple glow; more tends to turn muddy and harder to balance.
Why does my CSS mesh gradient look muddy?
Opposite hues meeting in sRGB blend through grey. Use neighbouring colours, reduce the overlap, or add in oklab to each layer.
Is a CSS mesh gradient bad for performance?
A static one is cheap. Costs rise with large blur filters and with animation, because the background repaints every frame.
Does SVG support mesh gradients?
Mesh gradients were drafted for SVG 2 but browsers never implemented them, so on the web you fake them with layers, use an image, or render them with WebGL.
Written by Gradiently
The team behind Gradiently, a design tool built around Marks: living gradients that make everything you design look like yours.
See our profile


