Noise
A grain overlay from an SVG turbulence that takes the plastic edge off gradients and accent surfaces.
One of 6 components in Backgrounds
Variants
Details
What Noise brings along, read from the registry and the source.
- Category
- Backgrounds
- Area
- Elements
- Variants
- 3
- Version
- 1.0.0 · since 1.0.0
- File
- components/ui/noise.tsx
- Brings along
- utilskit
- npm packages
- None
- Languages
- Texts come through props
- Studio
- Adjust in code
- Build with AI
- Over MCP in Claude, Cursor, Copilot and Codex
Props
The NoiseProps interface from components/ui/noise.tsx, with the comments from the code.
opacityoptionalnumber0 to 1, default 0.4. The grain itself is already thin, 1 is still subtle.
blendoptionalNoiseBlendnormaloverlaysoft-lightmultiplyscreen
How the grain mixes with what is below, default normal
grainoptionalNoiseGrainfinemediumcoarse
Size of the grain, default fine
toneoptional'foreground' | 'background' | 'contrast'Color of the grain: foreground (dark specks in light mode, light in dark), background (the opposite) or contrast (primary-foreground, for grain on an accent surface)
animatedoptionalbooleanLet the grain flicker like film, off with reduced motion
Accessibility
Read from the source of the component.
- Needs no client state. The server writes it in full, so it reads without JavaScript too.
Motion
- Brings animations of its own, calmly timed and without wobble.
- With reduced motion (prefers-reduced-motion) it stays still.
Source
The file aiw add puts into your project. From then on it is yours.
Show the file, 79 lines
'use client';
import { cn } from '@/lib/kit';
export type NoiseBlend = 'normal' | 'overlay' | 'soft-light' | 'multiply' | 'screen';
export type NoiseGrain = 'fine' | 'medium' | 'coarse';
export interface NoiseProps {
/** 0 to 1, default 0.4. The grain itself is already thin, 1 is still subtle. */
opacity?: number;
/** How the grain mixes with what is below, default normal */
blend?: NoiseBlend;
/** Size of the grain, default fine */
grain?: NoiseGrain;
/**
* Color of the grain: foreground (dark specks in light mode, light in dark),
* background (the opposite) or contrast (primary-foreground, for grain on an accent surface)
*/
tone?: 'foreground' | 'background' | 'contrast';
/** Let the grain flicker like film, off with reduced motion */
animated?: boolean;
className?: string;
}
const FREQ: Record<NoiseGrain, number> = { fine: 0.9, medium: 0.68, coarse: 0.45 };
const TONE = { foreground: 'var(--foreground)', background: 'var(--background)', contrast: 'var(--primary-foreground)' };
/*
* The grain is an SVG turbulence turned into an alpha map and used as a mask,
* so its color comes from a token and follows light and dark mode.
*/
const grainUrl = (freq: number, alpha: string) =>
`url("data:image/svg+xml;utf8,${encodeURIComponent(
`<svg xmlns='http://www.w3.org/2000/svg' width='220' height='220'><filter id='n' x='0' y='0'><feTurbulence type='fractalNoise' baseFrequency='${freq}' numOctaves='3' stitchTiles='stitch'/><feColorMatrix type='matrix' values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ${alpha}'/></filter><rect width='100%' height='100%' filter='url(%23n)'/></svg>`,
).replace(/%2523/g, '%23')}")`;
const CSS = `
@keyframes kitfx-noise-shift {
0% { transform: translate3d(0, 0, 0); }
25% { transform: translate3d(-37px, 23px, 0); }
50% { transform: translate3d(19px, -41px, 0); }
75% { transform: translate3d(-13px, -17px, 0); }
100% { transform: translate3d(31px, 11px, 0); }
}
@media (prefers-reduced-motion: reduce) { .kitfx-noise > * { animation: none !important; } }
`;
/**
* A grain overlay that takes the edge off flat gradients and photos. Put it
* after the background it should cover, it fills the parent and never takes
* clicks. Pure CSS and SVG, no canvas.
*/
export function Noise({ opacity = 0.4, blend = 'normal', grain = 'fine', tone = 'foreground', animated, className }: NoiseProps) {
// Dark specks where the noise is high, light specks where it is low: the grain adds texture, never a grey veil
const dark = grainUrl(FREQ[grain], '0 0 0 2 -1.06');
const light = grainUrl(FREQ[grain], '0 0 0 -2 0.94');
return (
<div aria-hidden className={cn('kitfx-noise pointer-events-none absolute inset-0 overflow-hidden', className)} style={{ opacity, mixBlendMode: blend }}>
{animated && <style dangerouslySetInnerHTML={{ __html: CSS }} />}
{[
{ mask: dark, color: TONE[tone] },
{ mask: light, color: tone === 'foreground' ? TONE.background : TONE.foreground },
].map((layer, i) => (
<div
key={i}
className={cn('absolute', animated ? '-inset-12' : 'inset-0')}
style={{
background: layer.color,
maskImage: layer.mask,
WebkitMaskImage: layer.mask,
maskSize: '220px 220px',
WebkitMaskSize: '220px 220px',
animation: animated ? 'kitfx-noise-shift 1.6s steps(1, end) infinite' : undefined,
}}
/>
))}
</div>
);
}Frequent questions
How do I install Noise?
With aiw add @ai-website/noise. The command puts components/ui/noise.tsx into your project, the code is yours from then on. It needs no further npm packages.
Which variants does Noise have?
3 variants, Soft grain, Film grain and On an accent card.
Can an AI assistant build with Noise?
Yes. Through the MCP server of the aiw CLI, Claude, Cursor, GitHub Copilot, Windsurf, Codex and Gemini find Noise with aiw_components and install it with aiw_add @ai-website/noise. Name the variant in your prompt, for example variant="vivid", and the assistant writes your content straight into the props.