EmpireUI
Get Pro
← Blog8 min read#noise#texture#css

CSS Noise Texture Effect: Grain, Grit and Analog Feel

Add film grain, SVG noise, and CSS texture overlays to your UI. Real techniques, no library needed — just CSS and a bit of SVG math.

Close-up of analog film grain texture on dark surface

Why Noise Texture? (And Why Designers Keep Coming Back to It)

Flat color is fast. Gradients add depth. But noise — actual randomized grain applied as an overlay — does something neither of those can: it makes your UI feel *physical*. Not skeuomorphic in the 2012 leather-stitching sense, just... tactile. Honest. Like it exists somewhere beyond a monitor.

Honestly, the resurgence makes total sense if you look at what's been dominating design Twitter since around 2023. Glassmorphism, aurora effects, claymorphism — they all share one quality: they simulate materials. Noise texture is the same impulse but stripped down. It doesn't need a backdrop-filter or a blur radius. A single SVG filter and a CSS pseudo-element gets you there.

Worth noting: this isn't just a vibe thing. Noise actually resolves a real perceptual problem called color banding — the visible stepped transition between adjacent shades in gradients, especially on OLED screens. A subtle noise overlay breaks up those bands and makes gradients look smoother even at low opacity (around 0.04–0.08 is the sweet spot most of the time).

So you're reading this either because something looks too digital and you want to rough it up, or you've seen that grainy card treatment in Figma and want to know how it actually works in production CSS. Either way, let's get into it.

The SVG feTurbulence Approach — The Right Way

The cleanest method for CSS noise uses an inline SVG filter with feTurbulence. No image file to load, no JavaScript, no canvas. Just markup and math. The filter generates Perlin noise or fractal noise directly in the browser.

Here's the baseline setup: ``html <svg xmlns="http://www.w3.org/2000/svg" style="display:none"> <filter id="noise-filter"> <feTurbulence type="fractalNoise" baseFrequency="0.65" numOctaves="3" stitchTiles="stitch" /> <feColorMatrix type="saturate" values="0" /> </filter> </svg> ` Then in your CSS: `css .grain-overlay::after { content: ''; position: absolute; inset: 0; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.65' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='1'/%3E%3C/svg%3E"); opacity: 0.05; pointer-events: none; border-radius: inherit; z-index: 1; } ``

The baseFrequency value controls grain size. Lower values like 0.35 give you coarse, almost sand-like texture. Higher values like 0.85 give you tight, film-grain noise. numOctaves controls detail layering — 3 is usually plenty; go to 4 or 5 and you're burning GPU cycles for no visible return.

stitchTiles="stitch" is the one flag you absolutely want. Without it, the noise pattern has visible seams when it tiles — and on large backgrounds, you'll see them. That single attribute tells the browser to blend the noise at tile boundaries. It costs almost nothing and fixes a real visual problem.

In practice, the data-URI approach (encoding the SVG inline into background-image) is the move for production. It avoids an extra network request and works in pseudo-elements, which is where you actually want this — layered on top of a background without polluting your DOM.

Layering Noise Over Gradients — Where It Gets Interesting

Noise by itself on a flat white card? Barely noticeable. But layer it over a gradient and you get something that looks like it was printed on premium matte paper. This is the combo behind most of those "expensive" UI cards you see on Dribbble.

Here's a gradient card with noise overlay: ``css .noise-card { position: relative; background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%); border-radius: 16px; overflow: hidden; padding: 32px; } .noise-card::before { content: ''; position: absolute; inset: 0; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.75' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E"); opacity: 0.04; pointer-events: none; border-radius: inherit; } ``

That 0.04 opacity is doing real work. You'd barely see the noise in isolation. But over the gradient it breaks up the color uniformity without drawing attention to itself — it just makes the card feel more *solid*. Crank it above 0.08 or 0.09 and it starts looking intentionally distressed rather than subtly textured. Know the difference.

One more thing — if you're building something in the glassmorphism family, stacking noise over a frosted-glass surface works incredibly well. The backdrop-filter: blur() creates a soft base, and the grain overlay gives it that tactile edge. Check the glassmorphism generator and imagine that frosted panel with a 0.04 noise overlay on top. That's the direction.

Quick aside: this also pairs naturally with aurora background effects. The animated gradient layers gain a ton of depth when you run a noise overlay across the whole viewport. The grain makes the aurora feel like it's behind a slightly textured glass panel instead of floating on pure CSS.

Animated Grain — Film Noise That Moves

Static grain is great. Animated grain is *cinematic*. If you've ever watched a film through a real projector, you know the noise shifts frame to frame. You can fake that in CSS by shifting the background-position of your noise overlay on a fast loop.

@keyframes grain-shift {
  0%, 100% { transform: translate(0, 0); }
  10%  { transform: translate(-2%, -3%); }
  20%  { transform: translate(3%, 2%); }
  30%  { transform: translate(-1%, 4%); }
  40%  { transform: translate(4%, -1%); }
  50%  { transform: translate(-3%, 3%); }
  60%  { transform: translate(2%, -4%); }
  70%  { transform: translate(-4%, 2%); }
  80%  { transform: translate(3%, -2%); }
  90%  { transform: translate(-2%, 4%); }
}

.animated-grain::after {
  content: '';
  position: fixed;
  inset: -50%;
  width: 200%;
  height: 200%;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.65' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  opacity: 0.06;
  pointer-events: none;
  animation: grain-shift 0.4s steps(1) infinite;
}

The steps(1) on the animation is key. Without it, the browser interpolates between keyframes and you get a sliding motion rather than a genuine frame-by-frame flicker. steps(1) forces hard cuts between positions — that's what creates the film grain illusion. Don't use linear here, ever.

The oversized element trick (200% width/height, inset -50%) means when the transform shifts the overlay, you never see the edge of the noise pattern. You're always looking at the interior of a bigger noise field. It's a bit wasteful on memory but the visual payoff is real.

Worth noting: the 0.4 second duration at 10 keyframes gives you 25 frames per second of grain variation. That maps to a 24fps film aesthetic almost exactly. Want more jitter? Drop it to 0.2s. Want that slower Super-8 feel? Go up to 0.6s.

Noise With CSS Blend Modes — Taking it Further

If you really want control, mix-blend-mode opens up a different dimension. Instead of just placing noise on top and lowering its opacity, you can use blend modes to interact the grain with the colors underneath — similar to how Photoshop's noise layers work.

.blend-grain::after {
  content: '';
  position: absolute;
  inset: 0;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.65' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  opacity: 0.15;
  mix-blend-mode: overlay;
  pointer-events: none;
  border-radius: inherit;
}

With mix-blend-mode: overlay, the noise brightens highlights and darkens shadows — which is exactly how real film grain behaves. You can run a higher opacity (0.12–0.20) because the blend mode naturally moderates the effect. soft-light is even subtler and works better on light backgrounds. multiply on a noise overlay over a white element darkens it believably, like aged paper.

Look, overlay is the workhorse. color-dodge on a dark background creates something that looks almost like light scattering through a frosted lens — useful if you're building something in the cyberpunk or vaporwave aesthetic where chromatic excess is the whole point. Don't use it on anything that needs to feel calm.

One combination that consistently works: gradient background + overlay noise + a very subtle box shadow around the card perimeter. The shadow grounds the element, the gradient gives it dimension, and the noise makes it feel tangible. Three layers, totally CSS, no images, no JS.

Performance, Browser Support, and When to Skip It

Browser support for feTurbulence as of 2026 is essentially universal — Chrome, Firefox, Safari, Edge, all fine. The SVG filter engine has been stable for years. What you do need to watch is rendering performance, especially on mobile.

The SVG noise approach is GPU-accelerated via the compositor when applied as a background-image (not as a CSS filter on the element itself — don't do that). The pseudo-element with pointer-events: none keeps it out of the event system. Add will-change: transform if you're animating it, but skip that on static grain — it just wastes a GPU layer.

On low-end Android devices, the animated grain variant can drop frames if the underlying page has a lot of other composited layers. Use @media (prefers-reduced-motion: reduce) to kill the animation and fall back to static noise. Static grain has near-zero performance cost; animated grain has real overhead on constrained hardware.

@media (prefers-reduced-motion: reduce) {
  .animated-grain::after {
    animation: none;
  }
}

When should you skip noise entirely? On data-dense UIs — tables, dashboards, anything where precision is the point. Noise is a mood tool. It's at home on landing pages, hero sections, cards, buttons with a stylistic bent, full-bleed backgrounds. It doesn't belong on a financial reporting table or an IDE. Know the context and you won't go wrong.

Noise in the Wild — Design Systems Using It Right

The best implementations of CSS noise texture you'll find right now use it as a secondary layer — never the hero. Stripe's landing pages have used it. Linear's app uses extremely subtle grain on dark surfaces. Vercel's design team has shipped it on marketing pages. All of them at under 0.06 opacity.

If you're building a design system and want a consistent noise token, define it as a CSS custom property: ``css :root { --noise-sm: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E"); --noise-md: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.65' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E"); --noise-lg: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.35' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E"); --noise-opacity: 0.05; } ``

That gives you three grain scales — fine (0.85), medium (0.65), coarse (0.35) — and a single opacity token you can tune globally. It's the kind of system that makes a neobrutalism design feel hand-printed and a glassmorphism card feel physically frosted rather than digitally blurred.

Empire UI's component library treats texture as a first-class design concern. If you're exploring what noise looks like paired with real components — cards, buttons, backgrounds — browse the components and look at the style variants. You'll see where subtle grain has been applied and where it's been intentionally left out.

FAQ

Does CSS noise texture hurt page performance?

Static SVG noise as a background-image is near-zero cost — the browser caches the data URI and composites it on the GPU. Animated grain costs more; use prefers-reduced-motion to disable it on constrained devices.

What's the difference between feTurbulence type 'turbulence' and 'fractalNoise'?

turbulence generates sharp, high-contrast Perlin noise that looks more like marble or clouds. fractalNoise produces smoother, more evenly distributed grain — it's the right choice for film grain effects.

Can I use this in Tailwind CSS projects?

Yes, but Tailwind doesn't have a noise utility out of the box. Drop the SVG data URI into a custom CSS class or extend your theme with a backgroundImage key. The pseudo-element approach works fine alongside Tailwind.

How do I make the noise only show on dark backgrounds?

Use mix-blend-mode: overlay on the noise pseudo-element. On dark surfaces the grain brightens highlights and reads clearly; on white it's nearly invisible — which is exactly the behavior you want.

Free components in 40 styles
React & Tailwind, copy-paste ready.
Browse →

Read next

Noise + Gradient in CSS: The Texture Combo Designers Love in 2026Grain & Noise Texture in CSS: SVG Filters, Canvas and Pure CSSCSS Box Shadow: The Complete Guide With Live ExamplesTailwind Responsive Design: The Breakpoints No One Talks About