# Grain

A film-grain overlay that removes banding from gradients, with an optional jitter.

**Category:** Backgrounds  
**Exports:** `Grain`  
**npm dependencies:** none (+ clsx, tailwind-merge for `cn`)

## Install

```bash
npx shadcn@latest add https://charcoalui.com/r/grain.json
```
The file lands at `components/ui/grain.tsx`; import from `@/components/ui/grain`.

## API — Grain
| Prop | Type | Default | Notes |
| --- | --- | --- | --- |
| `opacity` | `number` | `0.08` |  |
| `animated` | `boolean` | `true` |  |

## Example — Grain

```tsx
import { Grain } from "@/components/ui/grain";

export default function GrainDemo() {
  return (
    <div className="grid w-full gap-3 sm:grid-cols-2">
      <div className="relative grid h-56 place-items-center overflow-hidden rounded-xl bg-[radial-gradient(circle_at_30%_120%,#0285f7,#151515_65%)] text-sm text-white/80">
        No grain
      </div>
      <div className="relative grid h-56 place-items-center overflow-hidden rounded-xl bg-[radial-gradient(circle_at_30%_120%,#0285f7,#151515_65%)] text-sm text-white/80">
        <Grain opacity={0.35} />
        <span className="relative">With grain</span>
      </div>
    </div>
  );
}
```

## Source — components/ui/grain.tsx

```tsx
import * as React from "react";
import { cn } from "@/lib/utils";

export type GrainProps = {
  /** 0–1. */
  opacity?: number;
  /** Jitter the noise so it feels like film. */
  animated?: boolean;
  className?: string;
};

const noise =
  "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E\")";
const css = `@keyframes charcoal-grain{0%,100%{transform:translate(0,0)}20%{transform:translate(-3%,2%)}40%{transform:translate(2%,-3%)}60%{transform:translate(-2%,3%)}80%{transform:translate(3%,-1%)}}`;

/** Film grain overlay. Put it last inside a `relative` element. */
export function Grain({ opacity = 0.08, animated = true, className }: GrainProps) {
  return (
    <div aria-hidden className={cn("pointer-events-none absolute inset-0 overflow-hidden", className)}>
      <style href="charcoal-grain" precedence="default">
        {css}
      </style>
      <div
        className={cn("absolute -inset-[10%] mix-blend-overlay", animated && "[animation:charcoal-grain_0.6s_steps(4)_infinite] motion-reduce:[animation:none]")}
        style={{ backgroundImage: noise, opacity }}
      />
    </div>
  );
}
```