# Mesh Gradient

> Animated WebGL mesh gradient that blends up to six colours in OKLab, with distortion, swirl, grain and a pointer lens — for heroes and empty states.

- **Slug:** `mesh-gradient` · **Category:** effects · **Status:** beta
- **Import:** `import { MeshGradient } from "@kura-ui/react/mesh-gradient";`
- **Docs:** https://kuro.abstergo.fyi/components/mesh-gradient
- **Registry item:** https://kuro.abstergo.fyi/r/mesh-gradient.json
- **Tags:** gradient, mesh, webgl, shader, background, hero, oklab, ambient

## Install

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

## When to use

- A hero, landing or onboarding section that needs a calm, living background behind a headline.
- An empty state or a 'start a conversation' panel that should feel warm instead of blank.
- A card or banner announcing an AI feature, using the theme-aware `signal` preset.
- Any container where content should sit on a soft, slowly moving colour field (pass children).

## When not to use

- Northern-lights curtains or a night-sky feel — use aurora.
- Showing that an agent is listening or speaking — use agent-orb.
- Signalling that a container is busy — use border-beam (it outlines instead of filling).
- Small inline indicators — use thinking-indicator or shimmer-text.
- Just adding texture to an existing background — use grain.
- A fully custom GLSL effect — build it on shader-canvas.

## Props

| Prop | Type | Default | Required | Description |
| --- | --- | --- | --- | --- |
| `colors` | `"signal" \| "paper" \| "jade" \| "dusk" \| "ember" \| "glacier" \| readonly string[]` | `"signal"` | no | A preset, or 2–6 CSS colours (OKLCH, hex, rgb, `var(--kura-*)`). `signal` and `paper` are built from tokens and follow light/dark; the others are fixed palettes. |
| `distortion` | `number` | `0.5` | no | Organic noise warping of the colour field, 0–1. |
| `swirl` | `number` | `0.25` | no | Twist around the centre, 0–1. Keep ≤ 0.5 behind text. |
| `speed` | `number` | `1` | no | Animation speed multiplier. 0 renders a still frame. |
| `grain` | `number` | `0.12` | no | Film-grain amount, 0–1. A little grain hides banding on large surfaces. |
| `interactive` | `boolean` | `true` | no | Gently lens the colour field toward the pointer while it is over the element. |
| `paused` | `boolean` | `false` | no | Freeze the animation on the current frame. |
| `seed` | `number` | `0` | no | Changes where the colour wells start, so two gradients on a page differ. |
| `maxDpr` | `number` | `1.5` | no | Device-pixel-ratio cap. The field is soft, so 1–1.5 keeps it cheap. |
| `children` | `React.ReactNode` | — | no | Content rendered on top of the gradient (it sits in normal flow). |

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

## Anatomy

- **MeshGradient** (`data-slot="mesh-gradient"`) — Root container (`relative isolate overflow-hidden`). Size it with classes. `data-state` = animating | still.
- **ShaderCanvas** (`data-slot="mesh-gradient-canvas"`) — The WebGL layer behind the children (aria-hidden).
- **Fallback** (`data-slot="mesh-gradient-fallback"`) — Layered CSS radial gradients shown when WebGL2 is unavailable.

## Accessibility

- The gradient is decorative and aria-hidden; children keep their normal semantics.
- Text on top needs contrast: use `signal`/`paper` with text-kura-fg, or a dark preset with light text, and check both themes.
- The pointer lens is purely visual; nothing is interactive about the gradient itself.
- Reduced motion: Renders a single still frame (no flow, no pointer lens). Theme changes still redraw it.

## Notes for agents

- Always give the root a size (e.g. `className="h-64 w-full rounded-kura-xl"` or `min-h-80`); it has no intrinsic height without children.
- Children render in normal flow on top — use the root as the section itself (add padding/flex), not as an absolutely positioned layer.
- Custom colours are resolved once per theme change; CSS variables work (`['var(--kura-accent)', 'oklch(0.9 0.04 90)']`).
- Pair with `<Grain />` as the last child for a print-like finish on large heroes.
- Use at most one or two animated gradients per page; give each a different `seed`.

## Examples

### Theme-aware signal gradient (minimal)

Give it a size; the default `signal` preset follows light and dark.

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

export function MeshGradientMinimal() {
  return <MeshGradient className="h-64 w-full rounded-kura-xl shadow-kura-sm" />;
}
```

### Presets and controls (full)

Every preset plus distortion, swirl, speed, grain and pause.

```tsx
"use client";

import * as React from "react";
import { Button } from "@kura-ui/react/button";
import { MeshGradient, type MeshGradientPreset } from "@kura-ui/react/mesh-gradient";

const presets: MeshGradientPreset[] = ["signal", "paper", "jade", "dusk", "ember", "glacier"];

function Slider({
  label,
  value,
  onChange,
  max = 1,
  step = 0.05,
}: {
  label: string;
  value: number;
  onChange: (v: number) => void;
  max?: number;
  step?: number;
}) {
  return (
    <label className="flex flex-col gap-1.5 text-kura-fg-muted text-xs">
      <span className="flex justify-between">
        {label}
        <span className="font-kura-mono text-kura-fg-subtle tabular-nums">{value.toFixed(2)}</span>
      </span>
      <input
        type="range"
        min={0}
        max={max}
        step={step}
        value={value}
        onChange={(e) => onChange(Number(e.target.value))}
        className="accent-kura-accent"
      />
    </label>
  );
}

export function MeshGradientFull() {
  const [preset, setPreset] = React.useState<MeshGradientPreset>("jade");
  const [distortion, setDistortion] = React.useState(0.6);
  const [swirl, setSwirl] = React.useState(0.35);
  const [speed, setSpeed] = React.useState(1);
  const [grain, setGrain] = React.useState(0.15);
  const [paused, setPaused] = React.useState(false);
  return (
    <div className="flex w-full max-w-2xl flex-col gap-4">
      <MeshGradient
        colors={preset}
        distortion={distortion}
        swirl={swirl}
        speed={speed}
        grain={grain}
        paused={paused}
        seed={2}
        className="aspect-[16/9] w-full rounded-kura-xl shadow-kura-md"
      />
      <div className="flex flex-wrap items-center gap-1.5">
        {presets.map((p) => (
          <Button
            key={p}
            size="sm"
            variant={p === preset ? "secondary" : "ghost"}
            aria-pressed={p === preset}
            onClick={() => setPreset(p)}
            className="capitalize"
          >
            {p}
          </Button>
        ))}
        <Button size="sm" variant="ghost" className="ml-auto" onClick={() => setPaused((v) => !v)}>
          {paused ? "Play" : "Pause"}
        </Button>
      </div>
      <div className="grid grid-cols-2 gap-x-6 gap-y-3 sm:grid-cols-4">
        <Slider label="Distortion" value={distortion} onChange={setDistortion} />
        <Slider label="Swirl" value={swirl} onChange={setSwirl} />
        <Slider label="Speed" value={speed} onChange={setSpeed} max={3} step={0.1} />
        <Slider label="Grain" value={grain} onChange={setGrain} />
      </div>
    </div>
  );
}
```

### Hero section (composition)

Headline, badge and Button rendered as children on top of the gradient.

```tsx
import { ArrowRight } from "lucide-react";
import { Button } from "@kura-ui/react/button";
import { MeshGradient } from "@kura-ui/react/mesh-gradient";

export function MeshGradientHero() {
  return (
    <MeshGradient
      colors="signal"
      distortion={0.7}
      swirl={0.3}
      className="flex min-h-80 w-full flex-col items-start justify-end gap-4 rounded-kura-2xl p-8 shadow-kura-md"
    >
      <span className="rounded-full bg-kura-overlay px-2.5 py-1 font-medium text-kura-accent-text text-xs shadow-kura-xs backdrop-blur-xl">
        Agents · Beta
      </span>
      <h2 className="max-w-md text-balance font-medium text-3xl text-kura-fg tracking-tight">
        Hand off the busywork. Keep the judgement.
      </h2>
      <p className="max-w-sm text-kura-fg-muted text-sm leading-relaxed">
        Kura agents read your repo, plan the change and wait for your approval before touching
        anything.
      </p>
      <Button>
        Start a run <ArrowRight />
      </Button>
    </MeshGradient>
  );
}
```

## Dependencies

- npm: none
- Kura registry items: `kura-theme`, `kura-merge-refs`, `kura-shader-color`, `shader-canvas`, `use-reduced-motion`, `utils`
- Related: `aurora`, `grain`, `shader-canvas`, `agent-orb`, `particle-field`
