
Why a gradient mesh becomes a maintenance problem
A gradient mesh in Illustrator, Photoshop, or Figma gives designers smooth multi-point color blends, but the moment that artwork leaves the design tool it becomes a maintenance burden. The file is usually exported as a raster image or an SVG, the source colors live inside an opaque binary or vector asset, and every change, whether a brand refresh, a hover state, a dark mode, or a new breakpoint, means reopening the original project and re-exporting.
Web developers also lose a few practical capabilities in the export. The background cannot be themed through custom properties, the element cannot adapt to component state, and any responsive scaling risks blur, banding, or recompression artifacts. Even a deliberately compressed PNG adds a network request and a paint cost on top of the design lock-in.
CSS has no widely-supported mesh-gradient function yet, but four layered radial gradients over a single base color reproduce the common corner-blob look the design tools call a gradient mesh. The result is plain CSS, scales with the element, and edits live in code.
How layered CSS radial gradients replace a gradient mesh
The approach is a paint-order stack of four background-image layers, one radial-gradient(circle at x y, color, transparent X%) per color, listed in the background-image declaration so the first layer paints on top. The fifth declaration, background-color, fills the area that the gradients do not cover and acts as a predictable fallback.
The Mesh Gradient Generator fixes the four focal positions near the corners of the element so the result looks balanced without manual tuning:
| Layer | Position (x y) | Role |
|---|---|---|
| 1 | 15% 20% | Top-left color island |
| 2 | 85% 15% | Top-right color island |
| 3 | 20% 85% | Bottom-left color island |
| 4 | 80% 80% | Bottom-right color island |
Each layer begins at its selected color at 0% and transitions to transparent at the spread percentage, so the visible color forms an island that fades into the base color or into a neighboring island. Spread controls island size; smaller values produce compact spots with more base color between them, and larger values let the islands grow until they overlap and blend.
How does the CSS radial layer approach compare to the other common ways teams typically replace a gradient mesh in production code?
| Approach | Output format | Editable in code | Extra request |
|---|---|---|---|
| Layered CSS radial gradients | CSS only | Yes, in stylesheet | None |
| Exported PNG or WebP | Raster image | No, locked binary | One image |
| Exported SVG | Vector markup | Yes, XML edits | One SVG |
| Canvas or WebGL paint | Runtime pixels | JavaScript-driven | Library weight |
Layered CSS radial gradients win on editability and network cost; raster exports win on visual fidelity for photographic blends; SVG wins when the asset must scale and stay editable but cannot be expressed in CSS; canvas or WebGL wins when the mesh must change at runtime. For the typical hero, card, or section background, the CSS layer approach is the easiest to ship and the cheapest to maintain.
Replace a gradient mesh using the Mesh Gradient Generator
The Mesh Gradient Generator is a small in-browser tool that exposes five color fields, a single spread control, and a live CSS preview. Everything happens in the current tab, so no palette, preview, or screenshot is uploaded or saved, and the preview renders the exact declarations that ship.
- Choose four mesh colors and a base background color. Each color is a six-digit hexadecimal value validated before generation, which is also the form the browser color inputs emit. Pick the color you want at each of the four corners and a base color that fills any transparent gap.
- Adjust the fade spread while watching the live CSS preview. Spread is an integer from 10 to 90. Increase it to make each color reach further before becoming transparent, decrease it to keep colors compact and let the base color dominate.
- Copy the complete background declarations. The output is a background-color and a background-image declaration with four comma-separated radial-gradient(...) layers. Paste the block into a class, a custom property, a CSS module, or a style sheet.
- Test contrast, paint cost, and cross-browser rendering in the real component before shipping. The preview uses the exact declarations shown in the output, so any mismatch you see in the page is a real rendering difference, not a preview issue.
What the generated CSS actually contains
The output is intentionally short so it stays readable in code review and easy to retest. A representative block sets a single background-color and four comma-separated radial-gradient(...) layers in the background-image declaration, with the first listed layer closest to the viewer. Each gradient uses circle, an explicit position, a zero-percent color stop, and a transparent terminal stop, so the syntax is standard and inspectable.
The background-color line sits behind the layers, fills the corners the gradient circles do not cover, and provides a fallback if any single layer is removed. The generator does not add background-size, background-repeat, background-attachment, background-clip, or animation rules by default. Add those only when the design actually requires them, and the goal is to keep the background as cheap to paint as possible.
Every value is integer- or hex-bounded: the spread is an integer from 10 to 90, and the colors are six-digit hex. That makes the copied output deterministic and easy to lock down with tests. The tool ships fixtures that assert the transparent stop at spreads from 20 to 90, lock four unique positions, validate the hex format, and pin the spread boundaries.
Test contrast, paint cost, and rendering before shipping
A visually attractive gradient can still be a usability trap. The background color changes position by position as translucent layers overlap, so a single foreground color may meet contrast in one corner and fail in another. Test headings, body copy, links, focus indicators, and form controls across the entire box, not just at the center. The tool does not calculate WCAG contrast because the background varies continuously, so this is a manual step.
For long passages of text, consider a translucent surface layer or a simpler solid section behind the copy. Respect forced-colors mode, high-contrast mode, reduced motion, and print styles; users on those modes should still see readable text and a sensible background.
Layered gradients are usually lightweight, but large animated backgrounds and frequent style changes can increase paint cost and battery use. Prefer a static background for normal page sections, and if you animate positions or colors, measure on representative mobile hardware and honor prefers-reduced-motion. Avoid huge offscreen elements and excessive filter effects. CSS gradients can also render with small differences across browsers, GPUs, color spaces, screenshots, and exported PDFs, so inspect the actual target environment rather than trusting a single screenshot.
When this approach is the right fit
Layered CSS radial gradients are a strong fit when the gradient mesh in question is the common design-system pattern: four focal points near the corners, smooth blends toward a base color, no directional control points inside the box. Hero sections, login screens, card backgrounds, and section dividers are typical targets. For design systems, move the final colors into named tokens, document the intended foreground treatment, and add visual regression coverage at the component sizes you ship.
The approach is less suitable for free-form gradient meshes with many internal control points, sharp color boundaries, or photographic blends. Those still need SVG, a canvas implementation, or a raster export. In those situations, consider whether a design tool workflow like matching exact Figma gradient stops can give you tighter authoring control before you fall back to raster.
Once you have a CSS block you are happy with, paste it into your stylesheet, theme the colors through custom properties, and let the element respond to component state without re-exporting an image every time the brand changes.