# CSS mesh gradient: how to build one from radial layers

[Canonical HTML page](https://gradiently.design/guide/css-mesh-gradient)

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.

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

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](https://gradiently.design/guide/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.

- Pool 1: violet, top left: `radial-gradient(circle at 15% 20%, #7c3aed, #0b1026 55%)`
- Pool 2: pink, top right: `radial-gradient(circle at 85% 25%, #ec4899, #0b1026 50%)`
- Pool 3: cyan, bottom: `radial-gradient(ellipse at 60% 90%, #06b6d4, #0b1026 60%)`
- Pool 4: amber accent: `radial-gradient(circle at 25% 80%, #f59e0b, #0b1026 45%)`

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. **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](https://gradiently.design/guide/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 oklab` to each layer, as in `radial-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.

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

> **Blur is the expensive part** A static blur is painted once and is usually fine. A blurred layer that moves or resizes is repainted every frame, and large blur radii at full screen cost the most. See [CSS gradient performance](https://gradiently.design/guide/css-gradient-performance).

## 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](https://gradiently.design/guide/css-noise-texture), and the design reasoning in [grainy gradients](https://gradiently.design/guide/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](https://gradiently.design/guide/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](https://gradiently.design/guide/css-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 |

Pick the lightest tool that still looks right.

Shaders are covered in [WebGL gradients](https://gradiently.design/guide/shader-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.

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

## FAQ

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