# Metaballs

Soft blobs that drift, merge and split like a lava lamp, drawn as a dither.

> 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/metaballs.json](https://dithercn.dev/r/metaballs.json), under `files[].content`.

## Installation [#installation]

```bash
npx shadcn@latest add https://dithercn.dev/r/metaballs.json
```

## Usage [#usage]

```tsx
import { Metaballs } from "@/components/dithercn/metaballs";

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

Each blob rises and sinks on its own slow loop. Where two come close, their edges reach for each other and join, then pull apart again. Every blob is densest at its core and has a soft halo around its edge.

## Examples [#examples]

### Contour rings [#contour-rings]

`variant="rings"` draws contour lines around the blobs instead of filling them. The rings merge and split with the blobs.

```tsx title="examples/metaballs-rings.tsx"
import { Metaballs } from "@/components/dithercn/metaballs";

export default function MetaballsRings() {
  return (
    <Metaballs
      variant="rings"
      count={4}
      cell={2}
      className="absolute inset-0"
    />
  );
}
```

### Lava lamp [#lava-lamp]

Five blobs in warm colours, sped up a little, in a tall rounded box. In a narrow box the blobs are sized by its width.

```tsx title="examples/metaballs-lamp.tsx"
import { Metaballs } from "@/components/dithercn/metaballs";

export default function MetaballsLamp() {
  return (
    <div className="border-border relative h-96 w-56 overflow-hidden rounded-[3rem] border [--dither-paper:var(--background)]">
      <Metaballs
        count={5}
        size={0.95}
        speed={1.2}
        palette={[
          "--dither-paper",
          "oklch(0.45 0.16 20)",
          "oklch(0.68 0.2 40)",
          "oklch(0.85 0.15 80)",
        ]}
        className="absolute inset-0"
      />
    </div>
  );
}
```

## Props [#props]

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `variant` | `"solid" \| "rings"` | `"solid"` | Shaded blobs with a soft halo, or contour lines around them. |
| `count` | `number` | `5` | How many blobs, from 2 to 8. |
| `size` | `number` | `1` | Size of the blobs. In a tall box they are sized by the width. |
| `palette` | `[DitherColor, DitherColor, ...DitherColor[]]` | `["--dither-paper", "--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"` | `"bayer4"` | 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 soft CSS blobs 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).