81 / Scenes
Metaballs
Blobs that swell into one another as they meet, taking their two colours from your theme.
Gooey
"use client"
import { Metaballs } from "mischief-ui/metaballs"
export function MetaballsDemo() {
return (
<Metaballs
count={5}
radius={0.11}
className="border-border w-full max-w-xl rounded-[var(--radius)] border"
>
<div className="px-8 py-20 text-center">
<h3 className="text-3xl font-semibold">Gooey</h3>
</div>
</Metaballs>
)
}Installation
Copy the source into your project, or keep it behind a package.
npx shadcn@latest add Tinkerers-Labs/mischief-ui/metaballsimport { 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.
"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.
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.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.