Noise Mesh
Mesh gradient built from 5 blurred radial spots, animated in pure CSS and overlaid with an feTurbulence grain texture — organic and textured with zero JavaScript.
You'll get access to the interactive demo with a free account.
This effect is part of Effect.Labs — 811 vanilla effects, some free, some premium. Backgrounds has 50 effects, including 6 free. Explore the category →
3 usage examples



How it works
The colored background is built from 5 stacked radial-gradient declarations in the ::before pseudo-element, each anchored to a different point (at 30% 20%, at 70% 35%, at 45% 75%, at 80% 70%, at 15% 60%). The pseudo-element overflows by 25% on each side (inset: -25%) to avoid hard edges during animation. A filter: blur(28px) merges the 5 color spots into a continuous mesh gradient.
The @keyframes noiseFloat animation applies a translate + scale + rotate combination to the ::before pseudo-element over 12 s in alternate mode (auto ping-pong) — the background breathes continuously without any jump or hard reset. Only the pseudo-element moves; the container and its content stay fixed.
The grain layer is a <div class="noise-mesh-grain"> positioned at inset:0, z-index:1, displaying a tiled (128×128 px) inline SVG data URI containing an <feTurbulence type="fractalNoise" baseFrequency="0.85" numOctaves="4" stitchTiles="stitch"> filter. mix-blend-mode: overlay and opacity: 0.35 blend it over the gradient without drowning it out — producing a subtle organic texture with no external image.
The effect is 100% CSS, zero JavaScript. No requestAnimationFrame, no dependencies — drop it into a static component, an HTML email, or a static site generator without modification.
Accessibility
- prefers-reduced-motion: no rule present in the code — the animation always runs. Add
@media (prefers-reduced-motion: reduce) { .noise-mesh::before { animation: none; } }if the effect covers a large visible area. - No
aria-hiddenorrole="img"on the container in the provided HTML: if the background is purely decorative, addaria-hidden="true"to.noise-mesh. - The
.noise-mesh-labeltext (white at 85% opacity over a colorful gradient) has variable contrast depending on the underlying color — verify the WCAG AA ratio (≥ 4.5:1) in your context before shipping. - The effect contains no keyboard interaction or mouse events — no focus, no event handlers for assistive technologies to manage.
- The grain layer carries
pointer-events: none— it captures no clicks and does not interfere with clickable elements placed on top.
Browser compatibility
Requires filter: blur() on pseudo-elements and mix-blend-mode — supported in all modern browsers since 2021.
Without <code>filter: blur()</code> on pseudo-elements (very old browsers), the 5 color spots display without merging — harder-edged but still readable. If <code>mix-blend-mode</code> is absent, the grain renders in <code>normal</code> mode and lightly veils the gradient without altering it.
The code
HTML structure to paste into your page (CSS + JS available with a premium account):
<div class="noise-mesh">
<div class="noise-mesh-grain"></div>
<!-- Optional overlay content -->
<span class="noise-mesh-label">Your text</span>
</div>
Full HTML + CSS + JS, copy-paste ready — with hundreds of premium effects.
Customize
Options passed to the API or data-* attributes:
| Option / property | Default | Effect |
|---|---|---|
| 5 spot colors (rgba in ::before background) | rose, blue, green, amber, purple | Edit the 5 rgba() values in the ::before background property to match your visual identity. Keep opacities between 0.4 and 0.7 so the spots blend naturally after the blur. |
| filter: blur(28px) | 28px | Controls how smoothly the spots merge. Increase (40px+) for a softer, more abstract look; decrease (12px) for sharper transitions and more distinct colors. |
| animation-duration: 12s (in ::before animation) | 12s | Duration of one forward pass (direction reverses automatically). 20s = very gentle and subtle, 6s = more dynamic. |
| opacity: .35 (on .noise-mesh-grain) | 0.35 | Grain layer intensity. 0 = no grain, 0.6 = strong grain that dominates the gradient. |
| baseFrequency='0.85' (SVG feTurbulence in .noise-mesh-grain background-image) | 0.85 | Texture granularity in the SVG data URI. ~0.5 = coarse, visible grain; 1.2 = very fine, dense grain. |
| background-size: 128px 128px (on .noise-mesh-grain) | 128px 128px | Grain tile size. 64px = finer, more repeated; 256px = enlarged tile with wider patterns. |
| mix-blend-mode: overlay (on .noise-mesh-grain) | overlay | Grain blend mode over the gradient. Try soft-light for a subtler effect or screen for a brighter look. |
| border-radius: 16px (on .noise-mesh) | 16px | Container rounding. 0 = full-bleed square, high value = pill; 50% = circle if width equals height. |
FAQ
rgba(r,g,b,.xx) values in the background property of ::before. Each one is a spot anchored to a different position (at 30% 20%, at 70% 35%…). Keep opacities between 0.4 and 0.7 so blur(28px) blends the spots naturally.@media (prefers-reduced-motion: reduce) { .noise-mesh::before { animation: none; } }. The mesh gradient then displays statically — the grain texture stays visible, only the animation is disabled.