Grain Texture
A CSS or SVG noise overlay applied to backgrounds to add tactile depth and prevent gradients from looking flat — generated via SVG feTurbulence or a Base64 noise data URI.
Plain English
Smooth gradients look digital and synthetic. A thin layer of noise or grain — the same visual texture you see in film photography or premium print — makes a gradient feel physical and warm. Grain texture is the secret ingredient behind many "how did they make this look so good?" hero sections: the gradient itself might be unremarkable, but the fine noise overlay sitting above it at 6–10% opacity is what gives it depth and prevents the color from looking like a screensaver. The technique is borrowed from print design, where paper stock adds natural variation that digital screens lack. In UI, grain also serves a subtle function beyond aesthetics: it reduces Mach banding (the visible banding artifact that appears in gradients on some displays), making large gradient backgrounds look smoother on lower-quality monitors. Implementation requires either an SVG filter with feTurbulence applied to a full-screen pseudo-element, or a pre-generated noise image as a data URI tiled across the background.
Technical
SVG filter method (recommended, no asset needed): embed <svg> with <filter id="grain"><feTurbulence type="fractalNoise" baseFrequency="0.65" numOctaves="3" stitchTiles="stitch" /></filter> then on a pseudo-element: content: ""; position: absolute; inset: 0; background: url(#grain); opacity: 0.07; pointer-events: none; mix-blend-mode: overlay. Data URI method: use a PNG/WebP noise texture as background-image on an ::after pseudo-element with background-repeat: repeat; background-size: 200px 200px; opacity: 0.08. Animated grain: @keyframes grain-shift with transform: translate(random%) on the pseudo-element at 0%, 10%, 20%... 100% to make the grain appear to shimmer — used sparingly, 0.15s steps. Key properties: pointer-events: none (never intercept clicks), position: absolute with a positioned parent, and z-index layering above the background but below the content.
Live Demo
Grain Texture
A noise overlay softens digital-feeling gradients, giving them a tactile, printed quality.
Plain gradient
Digital & flat
No texture — feels sterile
Gradient + grain
Tactile & rich
Grain at 35% opacity
/* SVG feTurbulence noise overlay */
mix-blend-mode: overlay;
opacity: 0.35;
background: url('noise.svg') repeat;
Mix-blend-mode: overlay ensures the grain adapts to both light and dark areas of the gradient.
Usage
✓ Good usage
An aurora hero section with a grain-texture pseudo-element at 7% opacity and mix-blend-mode: overlay — the gradient transitions between colors smoothly and the surface feels tactile rather than digitally rendered.
✗ Bad usage
A data table with grain texture at 15% opacity applied to the entire page — the noise interferes with text legibility and makes numbers in the table harder to read quickly.
Recommended values
- Opacity: 5–10% for subtle texture (anything over 15% looks dirty)
- SVG baseFrequency: 0.55–0.75 (lower = coarser grain)
- numOctaves: 2–4 (more octaves = finer, more natural noise)
- mix-blend-mode: overlay or soft-light to integrate with colors below
- Background-size for tiled PNG: 150–250px (smaller = finer apparent grain)
- z-index: above background, below content (use isolation: isolate on parent)
Common mistakes
- Opacity too high — grain above 12% reads as a visual defect ("is my screen dirty?") rather than an intentional texture; stay in the 5–9% range for professional results.
- Missing pointer-events: none on the overlay pseudo-element — the grain layer sits above content and will intercept clicks on buttons and links if not explicitly disabled.
- Applying grain inside a stacking context with overflow: hidden — SVG filter-based grain requires the filter to be applied outside any overflow clipping or the effect is cut off at the element edges.
AI Prompt Example
Copy this into Claude, Cursor, Bolt, or v0.
Add a grain texture overlay to the hero section background. Create an ::after pseudo-element on the hero wrapper: position: absolute; inset: 0; pointer-events: none; opacity: 0.07; mix-blend-mode: overlay; background-image: url("data:image/svg+xml,...") (SVG with feTurbulence baseFrequency="0.65" numOctaves="3"). Ensure the hero wrapper has position: relative and the grain sits above the gradient layer but below all text and CTA elements using z-index. Test at different opacity values between 5% and 10% — choose whichever makes the gradient feel most tactile without looking dirty.