CSS and web

CSS mesh gradient: how to build one from radial layers

There is no mesh-gradient() in CSS. Here is the layering method that fakes one convincingly, the blur and grain that finish it, and when an image or a shader is the better call.

GradientlyVerified Gradiently account··5 min read
Cover: August Basin · GR·Y97R·RQ

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.

The four colour points of one mesh, each shown alone on the navy base. Stacked, they blend into a single field.
css
.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%);
}
The full stack. Change one at value and the whole composition rebalances.

Step by step: build your own CSS mesh gradient

  1. 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. 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. 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. 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. 5

    Mix in a better colour space

    Add in oklab to each layer, as in radial-gradient(in oklab circle at 15% 20%, ...), where supported. Overlaps stay brighter than in sRGB.

  6. 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.

css
.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);
}
A blurred mesh on a pseudo-element. Text inside .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.

css
@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; }
}
One pool drifting across the top. Add a second property per coordinate you want to move.

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

ApproachBest forWatch out for
Layered CSS gradientsStatic backgrounds, cards, heroes with three to five coloursRound-looking pools, banding without grain
CSS with blurSofter, more organic static fieldsRepaint cost if animated or resized often
Exported image (AVIF or WebP)Complex meshes with many points or painterly shapesFile weight, and a fixed crop per format
Video loopRich motion on a heroFile size, autoplay rules, reduced motion
WebGL shaderFull-screen, fluid, interactive motionCode complexity, battery use, fallbacks
Pick the lightest tool that still looks right.

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.

August Basin

GR·Y97R·RQ

August Basin

See this Mark
A Mark: colour pools, material and grain rendered live rather than approximated with layers.

Questions 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.

Share this guide

Written by GradientlyVerified Gradiently account

The team behind Gradiently, a design tool built around Marks: living gradients that make everything you design look like yours.

See our profile