# Mesh gradient

Three to five colour blobs that drift and melt into each other, drawn as a dither, for heroes and sign-up screens.

> For the complete documentation index, see [llms.txt](/llms.txt). Markdown variants are available by appending `.md` to any URL or sending an `Accept: text/markdown` header. The dithercn agent skill, for building any dither on the engine, is at [/.well-known/agent-skills/dithercn/SKILL.md](/.well-known/agent-skills/dithercn/SKILL.md).

> **This is an example, not the only option.** It is one component built on the dithercn engine. Install it when it is what you were asked for. For any other look, don't stretch its props to fit: write your own field on `DitherShader` ([Write your own dither](/docs/engine/write-your-own.md)), and read this example's source for technique. Its full source is in [https://dithercn.dev/r/mesh-gradient.json](https://dithercn.dev/r/mesh-gradient.json), under `files[].content`.

## Installation [#installation]

```bash
npx shadcn@latest add https://dithercn.dev/r/mesh-gradient.json
```

## Usage [#usage]

```tsx
import { MeshGradient } from "@/components/dithercn/mesh-gradient";

export function Hero() {
  return (
    <section className="relative isolate min-h-[480px]">
      <MeshGradient className="absolute inset-0 -z-10" />
      <h1>Your headline</h1>
    </section>
  );
}
```

Each blob drifts on its own slow orbit. Where two meet, their colours blend through the palette before the dither, so the seams read as soft gradients instead of edges. The default palette uses tints of your theme's `--dither-ink` with a touch of violet.

## Examples [#examples]

### Five blobs, warm palette [#five-blobs-warm-palette]

`palette` starts with paper and then takes one colour per blob. Pass `blobs + 1` colours to give each blob its own; the order sets which colours sit next to each other where blobs meet. Blue noise, the default, keeps the grain even across so many tones.

```tsx title="examples/mesh-gradient-sunset.tsx"
import { MeshGradient } from "@/components/dithercn/mesh-gradient";

export default function MeshGradientSunset() {
  return (
    <MeshGradient
      blobs={5}
      size={0.5}
      palette={[
        "--dither-paper",
        "oklch(0.42 0.16 300)",
        "oklch(0.55 0.2 350)",
        "oklch(0.66 0.19 25)",
        "oklch(0.76 0.15 55)",
        "oklch(0.88 0.11 85)",
      ]}
      matrix="blue-noise"
      className="absolute inset-0"
    />
  );
}
```

### Behind a form [#behind-a-form]

Three large blobs, bigger cells and a slower `speed` make a calm backdrop. The form sits on a translucent panel so the colour shows around it without hurting the text.

```tsx title="examples/mesh-gradient-signup.tsx"
import { MeshGradient } from "@/components/dithercn/mesh-gradient";

export default function MeshGradientSignup() {
  return (
    <div className="relative isolate flex size-full items-center justify-center p-6">
      <MeshGradient
        blobs={3}
        size={0.75}
        cell={5}
        speed={0.6}
        className="absolute inset-0 -z-10"
      />
      <form className="bg-background/85 border-border flex w-full max-w-xs flex-col gap-3 rounded-2xl border p-5 backdrop-blur-md">
        <div className="flex flex-col gap-1">
          <span className="font-medium">Join the waitlist</span>
          <span className="text-muted-foreground text-sm">
            One email when the beta opens.
          </span>
        </div>
        <label className="sr-only" htmlFor="mesh-gradient-email">
          Email
        </label>
        <input
          id="mesh-gradient-email"
          type="email"
          placeholder="you@company.com"
          className="bg-background border-input focus-visible:ring-ring/50 h-9 rounded-md border px-3 text-sm outline-none focus-visible:ring-[3px]"
        />
        <button
          type="button"
          className="bg-primary text-primary-foreground h-9 rounded-md text-sm font-medium transition-opacity hover:opacity-90"
        >
          Request access
        </button>
      </form>
    </div>
  );
}
```

## Props [#props]

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `blobs` | `3 \| 4 \| 5` | `4` | How many colour blobs drift across the box. |
| `size` | `number` | `0.6` | Size of each blob as a share of the box height. |
| `palette` | `[DitherColor, DitherColor, ...DitherColor[]]` | `Paper, then four tints of --dither-ink` | Colours from paper to ink: palette names such as "blue", CSS variable names, or any CSS colour. |
| `matrix` | `"bayer2" \| "bayer4" \| "bayer8" \| "blue-noise"` | `"blue-noise"` | The threshold pattern. See Dither engine. |
| `cell` | `number` | `--dither-cell` | Size of one dither cell in CSS pixels. |
| `speed` | `number` | `1` | How fast it moves. |
| `animate` | `boolean` | `true` | false stops the motion: a still frame if it starts off, the current frame if it was moving. |
| `className` | `string` | `–` | Size and position. The background fills this box. |

## Notes [#notes]

* It is decorative and hidden from assistive technology. Put real text on top of it, not inside it.
* With reduced motion turned on, it draws one still frame and stops.
* It pauses when scrolled out of view or when the tab is hidden, and releases its WebGL context when far offscreen. Without WebGL it shows a CSS approximation in the same colours.
* It costs one small draw per frame at 30 fps, at one pixel per dither cell. See [Performance](/docs/engine/performance).