Mischief

102 / Scenes

Metaballs

Blobs that swell into one another as they meet, taking their two colours from your theme.

Gooey

Installation

Copy the source into your project, or keep it behind a package.

npx shadcn@latest add Tinkerers-Labs/mischief-ui/metaballs
import { Metaballs } from "mischief-ui/metaballs"

Or paste it in yourself. The source imports the shared cn helper from @/lib/utils, so point that at your own copy.

registry/default/metaballs/metaballs.tsx
"use client" import * as React from "react"import {  RenderSurface,  createQuadProgram,  useThemeColors,  type QuadProgram,  type SurfaceColor,} from "@/registry/default/render-surface/render-surface"import { cn } from "@/lib/utils" const MAX_BALLS = 12 

Usage

export function Hero() {
  return (
    <Metaballs count={7} className="rounded-xl">
      <div className="px-8 py-20 text-center">
        <h1>Gooey</h1>
      </div>
    </Metaballs>
  )
}

How they merge

Each blob contributes a value to every pixel that falls away with the square of the distance to it. The colour changes where the total crosses a threshold. Nothing decides that two blobs are touching: they merge because their contributions add up, the same way two drops of water do.

The count is capped at twelve because every blob is another term evaluated at every pixel, and past a dozen the shape stops being readable long before the frame rate does.

The blobs also spread themselves to the shape of the box. Placed the same distance apart whatever the box looks like, they bunch into the middle third of a wide one and merge into a single mass, which reads as a glow rather than as a field of blobs.

Making the pointer one of them

With pointer on, wherever the reader is becomes another blob, merging with the others exactly as they merge with each other. It eases toward the pointer rather than being pinned to it, and fades in on arrival, so nothing pops into existence at the edge of the box.

The pointer is followed on the window rather than on this element, which is what lets the field sit behind other content and still answer to a pointer that never touches it. A backdrop that only reacts when nothing is on top of it is a backdrop that never reacts.

API

countnumberHow many blobs. Up to twelve. Defaults to 7.
basestringThe background. Defaults to "--background".
tintstringThe blobs. Defaults to "--primary".
speednumberMultiplies the drift. Defaults to 1.
radiusnumberSize of each blob, as a fraction of the shorter edge. Defaults to 0.16.
edgenumberHow sharply a blob ends. Lower is gooier. Defaults to 0.35.
pointerbooleanMakes the pointer one of the blobs, so it merges with the rest as it passes through them.

Accessibility

Decoration, hidden from assistive technology, behind its children on its own layer. Under reduced motion one frame is drawn and the blobs never move. If WebGL is unavailable the box keeps its ordinary background and its children.