# Particle Field

> Drifting particle constellation that parts around the pointer and can converge into a ring or point to signal “generating”.

- **Slug:** `particle-field` · **Category:** effects · **Status:** beta
- **Import:** `import { ParticleField } from "@kura-ui/react/particle-field";`
- **Docs:** https://kuro.abstergo.fyi/components/particle-field
- **Registry item:** https://kuro.abstergo.fyi/r/particle-field.json
- **Tags:** particles, constellation, network, canvas, background, generating, interactive

## Install

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

## When to use

- An empty state or hero for a knowledge/graph/agent product where a quiet network motif fits.
- Signalling that something is being generated: flip `converge` while the model works, release when done.
- A subtle, interactive backdrop behind a short prompt or call to action.

## When not to use

- Soft colour backgrounds — use mesh-gradient or aurora.
- Inline progress feedback — use thinking-indicator or shimmer-text.
- A voice agent's presence — use agent-orb.
- Behind dense content: lines and motion compete with text.

## Props

| Prop | Type | Default | Required | Description |
| --- | --- | --- | --- | --- |
| `count` | `number` | — | no | Number of particles. Default scales with the area (≈ one per 5,500 px², clamped 30–160). Links are O(n²); stay under ~180. |
| `linkDistance` | `number` | `110` | no | Max px distance at which two particles are joined by a hairline. |
| `speed` | `number` | `1` | no | Drift speed multiplier. |
| `interactive` | `boolean` | `true` | no | Particles part around the pointer and link to it in the accent colour. |
| `converge` | `boolean` | `false` | no | Gather the particles into `shape` in the accent colour (the generating signal). Set false to release them back into a drift. |
| `shape` | `"ring" \| "point"` | `"ring"` | no | ring: a slowly turning halo. point: a tight orbiting cluster. |
| `paused` | `boolean` | `false` | no | Freeze the field on the current frame. |
| `maxDpr` | `number` | `2` | no | Device-pixel-ratio cap. |
| `children` | `React.ReactNode` | — | no | Content rendered on top, in normal flow. |

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

## Anatomy

- **ParticleField** (`data-slot="particle-field"`) — Root (`relative isolate overflow-hidden text-kura-fg-subtle`). `data-state` = drifting | converging | still | fallback; `data-converge` mirrors the prop.
- **Canvas** (`data-slot="particle-field-canvas"`) — The 2D canvas behind the children (aria-hidden).
- **Fallback** (`data-slot="particle-field-fallback"`) — A static CSS dot grid shown when canvas 2D is unavailable.

## Accessibility

- Decorative: the canvas is aria-hidden and never takes pointer events from children.
- Converging is a visual signal only — pair it with text in a role=status region (e.g. “Generating…”).
- Reduced motion: No drift, travel or pointer reaction: one still frame. Toggling `converge` jumps straight to the gathered (or scattered) layout without animating.

## Notes for agents

- Particle and link colour = the root's text colour; recolour with a class (`text-kura-fg-muted`). Converged particles and pointer links use `--kura-accent`.
- Give the root a height and a background; the canvas is transparent.
- Use `converge` for indeterminate generation (seconds, not milliseconds) and release it when output appears.
- It pauses automatically offscreen; keep one field per view.

## Examples

### Drifting constellation (minimal)

Size it and give it a background; particles use the text colour.

```tsx
import { ParticleField } from "@kura-ui/react/particle-field";

export function ParticleFieldMinimal() {
  return (
    <ParticleField className="h-64 w-full rounded-kura-xl bg-kura-surface-sunken shadow-kura-sm" />
  );
}
```

### Converge, shapes and pause (full)

Toggle converge, switch ring/point, tune link distance and speed.

```tsx
"use client";

import * as React from "react";
import { Button } from "@kura-ui/react/button";
import { ParticleField, type ParticleFieldShape } from "@kura-ui/react/particle-field";

export function ParticleFieldFull() {
  const [converge, setConverge] = React.useState(false);
  const [shape, setShape] = React.useState<ParticleFieldShape>("ring");
  const [paused, setPaused] = React.useState(false);
  return (
    <div className="flex w-full max-w-2xl flex-col gap-4">
      <ParticleField
        converge={converge}
        shape={shape}
        paused={paused}
        linkDistance={120}
        speed={1.2}
        className="aspect-[16/9] w-full rounded-kura-xl bg-kura-surface-sunken text-kura-fg-muted shadow-kura-md"
      />
      <div className="flex flex-wrap items-center gap-1.5">
        <Button
          size="sm"
          variant={converge ? "accent" : "secondary"}
          aria-pressed={converge}
          onClick={() => setConverge((c) => !c)}
        >
          {converge ? "Release" : "Converge"}
        </Button>
        {(["ring", "point"] as const).map((s) => (
          <Button
            key={s}
            size="sm"
            variant={s === shape ? "secondary" : "ghost"}
            aria-pressed={s === shape}
            onClick={() => setShape(s)}
            className="capitalize"
          >
            {s}
          </Button>
        ))}
        <Button size="sm" variant="ghost" className="ml-auto" onClick={() => setPaused((p) => !p)}>
          {paused ? "Play" : "Pause"}
        </Button>
      </div>
    </div>
  );
}
```

### Generating state (composition)

A Button starts a 4s generation: particles gather into a ring while ShimmerText reports progress.

```tsx
"use client";

import * as React from "react";
import { Button } from "@kura-ui/react/button";
import { ShimmerText } from "@kura-ui/react/shimmer-text";
import { ParticleField } from "@kura-ui/react/particle-field";

export function ParticleFieldGenerating() {
  const [generating, setGenerating] = React.useState(false);
  React.useEffect(() => {
    if (!generating) return;
    const id = setTimeout(() => setGenerating(false), 4000);
    return () => clearTimeout(id);
  }, [generating]);
  return (
    <ParticleField
      converge={generating}
      className="flex h-80 w-full flex-col items-center justify-center gap-3 rounded-kura-2xl bg-kura-surface text-center shadow-kura-md"
    >
      <div role="status" aria-live="polite" className="h-6 text-sm">
        {generating ? (
          <ShimmerText>Generating your workspace…</ShimmerText>
        ) : (
          <span className="text-kura-fg-muted">Describe a workspace and Kura sets it up.</span>
        )}
      </div>
      <Button onClick={() => setGenerating(true)} disabled={generating}>
        Generate
      </Button>
    </ParticleField>
  );
}
```

## Dependencies

- npm: none
- Kura registry items: `kura-theme`, `kura-merge-refs`, `use-reduced-motion`, `utils`
- Related: `mesh-gradient`, `aurora`, `thinking-indicator`, `agent-orb`, `grain`
