Why designers add noise to flat surfaces
A flat colour fill or a smooth gradient is mathematically perfect and, on many displays, visibly so. Large gradients in particular show banding, where the eight bits per channel available in a normal display cannot represent the gradual change and the transition breaks into visible stripes. Adding a small amount of random per-pixel variation breaks the boundaries between those bands, so the eye reads a continuous ramp instead of steps. The same technique in print and audio is called dithering.
Beyond fixing banding, noise adds material character. A faint grain over a dark card reads as paper, film or fabric rather than as a rectangle of hex colour, which is why it appears so often in editorial layouts and in dark interfaces where flat panels can feel plasticky.
The amount needed is much smaller than most people expect. Somewhere between three and ten percent opacity over a large surface is usually enough to remove banding and add texture without the grain itself becoming a visible pattern.
How this generator builds the texture
The tool creates an offscreen canvas at the chosen size and fills an ImageData buffer one pixel at a time. For each pixel it draws a random number: if the number falls outside the density threshold the pixel is written as fully transparent, and if it falls inside, the pixel gets a colour and the alpha value derived from the opacity slider.
The monochrome option decides what that colour is. When it is on, one random value between 0 and 255 is written to all three channels, producing a grey speck. When it is off, each of the red, green and blue channels gets its own random value, producing coloured speckle that reads as chroma noise rather than film grain. Monochrome is the right default for almost every interface use.
The canvas is then exported with toDataURL as a PNG and wrapped in a CSS declaration, so the output is a line of the form background-image: url("data:image/png;base64,iVBORw0KGgo..."). Every pixel is independent white noise: there is no Perlin or simplex structure here, so the result has no clouds, no swirls and no scale, which is exactly what you want for grain.
Density, opacity and tile size in practice
Density is the proportion of pixels that receive any colour at all. At 100 percent every pixel is noise and the texture is dense static. At 20 percent only a fifth of the pixels are painted and the rest stay transparent, which produces sparse specks with the underlying colour showing through. Density controls how busy the grain looks; opacity controls how strongly it competes with what is behind it.
Tile size matters more than it first appears. The generated image is repeated across the surface by the browser's default background-repeat, so a small tile repeats often enough that the eye can pick out the recurring pattern, particularly on a large hero. A 64 pixel tile tiles visibly; 200 to 512 pixels is usually large enough that the repeat is not detectable. The cost is file size, since a noise PNG compresses poorly by nature and a large one can run to a few hundred kilobytes as a data URL.
Because random data is close to incompressible, PNG compression achieves very little here. If the data URL is larger than you want in your stylesheet, drop the tile size, lower the density so more pixels are transparent and compress better, or save the PNG as a separate file and reference it by URL so it can be cached independently of the CSS.
Applying the texture without hurting readability
The generated declaration can go straight onto an element, but layering it over an existing background is usually better. CSS allows multiple backgrounds, so background-image: url("data:image/png;base64,..."), linear-gradient(#1a1a2e, #0a0a14) puts the noise on top of the gradient in a single element with no extra markup.
For finer control, put the noise on a pseudo-element: give the container position: relative, then style ::after with content: "", position: absolute, inset: 0, the noise background, a low opacity, and pointer-events: none so it never intercepts clicks. That structure lets you fade the grain in and out, or disable it inside a media query, without touching the element's own background.
Two accessibility points are worth respecting. Keep the noise underneath text rather than over it, because grain reduces effective contrast and can push a passing colour pair below the WCAG threshold. And because a static grain is not motion, it is unaffected by prefers-reduced-motion; what you should honour instead is a design that still reads correctly if the texture fails to load.