Slopcheck
← All recipes

grain-overlay

Film grain overlay

A barely-there noise layer that makes flat gradients and big color fields feel printed and human instead of rendered.

All browsers (SVG turbulence as a data-URI background).

Use it for

  • Pages with large flat color or gradient areas
  • Brands going for editorial, analog, print or film vibes

Don't — this is how it turns into slop

  • Opacity above ~0.2 — it starts looking dirty, not intentional
  • Animated grain across the whole page: keep motion to one hero at most

Accessibility

  • pointer-events: none and aria-hidden — never blocks clicks or screen readers
  • Jitter only runs without prefers-reduced-motion

Performance

  • The noise tile renders once and repeats; the fixed layer is composited
  • The animated variant moves via transform only

The code

/* Film grain over the whole page — breaks the too-clean, rendered look
   of flat gradients and default UI. Static by default; optional subtle
   jitter for hero sections only. */
.grain {
  position: fixed;
  inset: -50%;
  z-index: 50;
  pointer-events: none;
  opacity: var(--grain-opacity, 0.14);
  /* overlay: mid-grey is neutral, so the same grain works on light and
     dark pages without a separate variant. */
  mix-blend-mode: overlay;
  /* Contrast is pushed inside the filter (slope 3.2) — raw fractal noise is
     too flat to read as grain at a sane opacity. */
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='220' height='220'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3CfeComponentTransfer%3E%3CfeFuncR type='linear' slope='3.2' intercept='-1.1'/%3E%3CfeFuncG type='linear' slope='3.2' intercept='-1.1'/%3E%3CfeFuncB type='linear' slope='3.2' intercept='-1.1'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23g)'/%3E%3C/svg%3E");
  background-size: 220px 220px;
}

@keyframes grain-jitter {
  0%   { transform: translate(0, 0); }
  20%  { transform: translate(-3%, 2%); }
  40%  { transform: translate(2%, -3%); }
  60%  { transform: translate(-2%, -1%); }
  80%  { transform: translate(3%, 3%); }
  100% { transform: translate(0, 0); }
}

/* steps() keeps it filmic instead of a smooth, seasick slide. */
@media (prefers-reduced-motion: no-preference) {
  .grain--animated {
    animation: grain-jitter 0.9s steps(5) infinite;
  }
}

@media print {
  .grain {
    display: none;
  }
}

Which of these fit your site?

Scan your URL for free. The full review picks recipes for each section and hands your AI a complete design kit.

Check my site ↑