# Grain

> Film-grain overlay from a cached SVG-noise tile, with blend modes and an optional compositor-only flicker — texture for gradients and heroes.

- **Slug:** `grain` · **Category:** effects · **Status:** beta
- **Import:** `import { Grain } from "@kura-ui/react/grain";`
- **Docs:** https://kuro.abstergo.fyi/components/grain
- **Registry item:** https://kuro.abstergo.fyi/r/grain.json
- **Tags:** grain, noise, texture, film, overlay, svg, background, effect

## Install

```bash
# shadcn registry (copies source into your project)
npx shadcn@latest add https://kuro.abstergo.fyi/r/grain.json
# Kura CLI (same, plus dependency resolution and --json output)
npx @kura-ui/cli add grain
# npm package
npm install @kura-ui/react
```

## When to use

- Adding a print-like finish on top of a mesh-gradient, aurora or photo.
- Hiding colour banding on large, low-contrast gradients.
- Giving a flat hero or empty-state panel a subtle tactile texture.

## When not to use

- As the background itself — combine it with mesh-gradient or aurora.
- Over body text or dense UI: noise reduces legibility.
- Signalling activity — grain is texture; use border-beam or thinking-indicator.

## Props

| Prop | Type | Default | Required | Description |
| --- | --- | --- | --- | --- |
| `opacity` | `number` | `0.12` | no | Strength of the grain, 0–1. 0.08–0.2 is subtle; 0.3+ is stylised. |
| `blend` | `"overlay" \| "soft-light" \| "multiply" \| "screen" \| "normal"` | `"overlay"` | no | mix-blend-mode. overlay works on mid and dark tones; multiply suits light surfaces; screen suits very dark ones. |
| `scale` | `number` | `1` | no | Grain size multiplier: 1 is fine film grain, 2–3 is coarse. |
| `animated` | `boolean` | `false` | no | Flicker the grain like projected film (a stepped transform, compositor-only). Off by default. |
| `fps` | `number` | `12` | no | Flicker rate when animated. 8–15 reads as film; higher looks like TV static. |

Native attributes of the root element, `className` and `ref` are passed through.

## Anatomy

- **Grain** (`data-slot="grain"`) — Absolutely positioned overlay (`inset-0`, pointer-events none, overflow hidden) carrying opacity and blend mode. `data-animated` mirrors `animated`.
- **Layer** (`data-slot="grain-layer"`) — Oversized noise layer (200%) that the flicker translates.

## Accessibility

- Decorative and aria-hidden; it never receives pointer events.
- Keep opacity low over text; grain can lower perceived contrast.
- Reduced motion: The flicker stops; the grain stays as a static texture.

## Notes for agents

- The parent must be positioned (`relative`) — Grain is `absolute inset-0`. Render it last so it sits on top, and lift text above it with `relative z-10` if needed.
- Server-safe: no hooks, no 'use client'.
- On near-white surfaces `overlay` is almost invisible by design; use `blend="multiply"` with opacity ~0.06 there.
- One Grain per surface; it tiles a 180px image, so cost does not grow with size.

## Examples

### Grain over a gradient (minimal)

Place it inside any positioned element.

```tsx
import { Grain } from "@kura-ui/react/grain";

export function GrainMinimal() {
  return (
    <div className="relative h-48 w-full max-w-md overflow-hidden rounded-kura-xl bg-[radial-gradient(120%_90%_at_20%_10%,var(--kura-accent-subtle),var(--kura-gray-3))] shadow-kura-sm">
      <Grain />
    </div>
  );
}
```

### Blend modes, opacity, scale, flicker (full)

Compare every blend mode on a light and a dark surface.

```tsx
"use client";

import * as React from "react";
import { Button } from "@kura-ui/react/button";
import { Grain, type GrainBlend } from "@kura-ui/react/grain";

const blends: GrainBlend[] = ["overlay", "soft-light", "multiply", "screen", "normal"];

export function GrainFull() {
  const [blend, setBlend] = React.useState<GrainBlend>("overlay");
  const [opacity, setOpacity] = React.useState(0.35);
  const [scale, setScale] = React.useState(1);
  const [animated, setAnimated] = React.useState(true);
  return (
    <div className="flex w-full max-w-xl flex-col gap-4">
      <div className="grid h-56 grid-cols-2 overflow-hidden rounded-kura-xl shadow-kura-sm">
        {["bg-kura-accent", "bg-kura-gray-12"].map((bg) => (
          <div key={bg} className={`relative ${bg}`}>
            <Grain opacity={opacity} blend={blend} scale={scale} animated={animated} fps={12} />
          </div>
        ))}
      </div>
      <div className="flex flex-wrap items-center gap-1.5">
        {blends.map((b) => (
          <Button
            key={b}
            size="xs"
            variant={b === blend ? "secondary" : "ghost"}
            aria-pressed={b === blend}
            onClick={() => setBlend(b)}
          >
            {b}
          </Button>
        ))}
        <Button
          size="xs"
          variant="ghost"
          className="ml-auto"
          aria-pressed={animated}
          onClick={() => setAnimated((a) => !a)}
        >
          Flicker {animated ? "on" : "off"}
        </Button>
      </div>
      <div className="grid grid-cols-2 gap-6 text-kura-fg-muted text-xs">
        <label className="flex flex-col gap-1.5">
          Opacity {opacity.toFixed(2)}
          <input
            type="range"
            min={0}
            max={0.6}
            step={0.01}
            value={opacity}
            onChange={(e) => setOpacity(Number(e.target.value))}
            className="accent-kura-accent"
          />
        </label>
        <label className="flex flex-col gap-1.5">
          Scale {scale.toFixed(1)}
          <input
            type="range"
            min={0.5}
            max={3}
            step={0.1}
            value={scale}
            onChange={(e) => setScale(Number(e.target.value))}
            className="accent-kura-accent"
          />
        </label>
      </div>
    </div>
  );
}
```

### On a mesh gradient (composition)

The classic pairing: MeshGradient with Grain as its last child.

```tsx
import { MeshGradient } from "@kura-ui/react/mesh-gradient";
import { Grain } from "@kura-ui/react/grain";

export function GrainOnGradient() {
  return (
    <MeshGradient
      colors="signal"
      className="flex h-64 w-full items-end rounded-kura-2xl p-6 shadow-kura-md"
    >
      <p className="relative z-10 max-w-xs font-medium text-kura-fg text-lg tracking-tight">
        A little grain turns a gradient into a print.
      </p>
      <Grain opacity={0.3} blend="overlay" />
    </MeshGradient>
  );
}
```

## Dependencies

- npm: none
- Kura registry items: `kura-theme`, `utils`
- Related: `mesh-gradient`, `aurora`, `shader-canvas`, `particle-field`
