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-bgUsage
Usagetsx
<NoiseFieldBg />Props
| Prop | Type | Default | Description |
|---|---|---|---|
| intensity | number | 1 | Grain strength: scales the layer opacity (1 = default ~10%, 0 = invisible). Default: 1. |
| filterId | string | noise-field-bg | 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. |
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