bottega

backgrounds

Noise Field

A server component that renders a subtle film-grain texture via a fractalNoise feTurbulence SVG filter (baseFrequency 0.65, 4 octaves) at low opacity (~10%). Static, zero JS. Theme-neutral: dark noise grains are visible on light backgrounds, bright grains on dark — consistent speckle on both. Place in a relative parent; the layer fills it absolutely with pointer-events-none. Pass a unique filterId when mounting multiple instances on one page.

Ported from AatreyuShau (MIT)

noise-field-bg

Install

1. Register the namespace (once per project):

json
// components.json — register the @bottega namespace once
{
  "registries": {
    "@bottega": { "url": "https://bottega.ariacode.ca/r/{name}.json" }
  }
}

2. Add the component:

bash
npx shadcn add @bottega/noise-field-bg

Usage

Usagetsx
<NoiseFieldBg />

Props

PropTypeDefaultDescription
intensitynumber1Grain strength: scales the layer opacity (1 = default ~10%, 0 = invisible). Default: 1.
filterIdstringnoise-field-bgPrefix for the inline SVG filter id. Override when mounting multiple NoiseFieldBg on the same page — SVG filter ids are document-global, two instances with the same id will clash. Default: "noise-field-bg". ponytail: stable fixed id; single-instance caveat documented here.

Source

noise-field-bg.tsxtsx
import type React from "react";

export interface NoiseFieldBgProps extends React.HTMLAttributes<HTMLDivElement> {
  /**
   * Grain strength: scales the layer opacity (1 = default ~10%, 0 = invisible).
   * Default: 1.
   */
  intensity?: number;
  /**
   * Prefix for the inline SVG filter id.
   * Override when mounting multiple NoiseFieldBg on the same page — SVG filter
   * ids are document-global, two instances with the same id will clash.
   * Default: "noise-field-bg".
   * ponytail: stable fixed id; single-instance caveat documented here.
   */
  filterId?: string;
}

/**
 * Subtle film-grain background layer (server component, zero JS).
 * A fractalNoise feTurbulence at high baseFrequency (0.65) produces fine
 * speckle rendered at ~10% opacity over var(--background).
 * Theme-neutral: reads as consistent grain on both light and dark because the
 * noise spans 0–1; dark grains are visible on light bg, bright grains on dark bg.
 * Place in a `relative` parent — fills it absolutely with pointer-events-none.
 *
 * ⚠ Single-instance caveat: filter id is stable (not per-render unique).
 * Mount two NoiseFieldBg on one page? Pass distinct `filterId` values.
 */
export function NoiseFieldBg({
  intensity = 1,
  filterId = "noise-field-bg",
  className = "",
  style,
  ...props
}: NoiseFieldBgProps) {
  // ponytail: 0.10 base opacity gives subtle but visible grain on both themes
  const opacity = Math.min(1, 0.1 * intensity);

  return (
    <div
      aria-hidden="true"
      className={["absolute inset-0 pointer-events-none overflow-hidden", className]
        .filter(Boolean)
        .join(" ")}
      style={{ backgroundColor: "var(--background)", ...style }}
      {...props}
    >
      {/* Full-bleed SVG grain: fractalNoise at high baseFrequency = film grain.
          No feDiffuseLighting — that required a light-color which killed the
          output on dark foregrounds (light theme). Plain noise at low opacity
          is symmetric: dark grains show on light bg, bright grains on dark bg. */}
      <svg
        aria-hidden="true"
        xmlns="http://www.w3.org/2000/svg"
        style={{
          position: "absolute",
          inset: 0,
          width: "100%",
          height: "100%",
          opacity,
        }}
      >
        <defs>
          <filter id={filterId} x="0%" y="0%" width="100%" height="100%">
            <feTurbulence
              type="fractalNoise"
              baseFrequency="0.65"
              numOctaves="4"
              stitchTiles="stitch"
            />
            {/* Desaturate: collapse RGB noise channels → neutral grayscale speckle */}
            <feColorMatrix type="saturate" values="0" />
          </filter>
        </defs>
        <rect width="100%" height="100%" filter={`url(#${filterId})`} />
      </svg>
    </div>
  );
}

Dependencies

  • @bottega/tokens