Skip to content
Added 13 Sept 2026
Back to library
metaball-lava
Metaball Lava
Backgrounds

Metaball Lava

Featured

Six inverse-square fields thresholded into one body, so blobs grow a neck as they merge. No amount of border-radius produces a neck.

Backgroundsmetaballbloblava lampimplicit surfacebackgroundshader
lines of CSS
24lines of CSS
dependencies
0dependencies
browser, for years
Everybrowser, for years
no motion at all
Noneno motion at all
Backdrop
Fragment shader, 3 files, no dependencies. The first one is this design. The other two are the runtime, shared by every shader effect in the catalog — copy them once and the next design you add is a single file. The HTML and CSS on this page are the preview surface and the gradient shown if WebGL is unavailable; they are not a substitute for these.
components/metaball-lava.tsx
'use client'

/**
 * Metaball Lava — six inverse-square fields, thresholded into one body.
 *
 * Blobs that merge and separate are the canonical demonstration of an
 * implicit surface: nothing here knows about a shape, only about a scalar
 * field that six points contribute to, and the shape is wherever that field
 * crosses 1. Two blobs touching produce the neck that makes the effect
 * recognisable, and no amount of `border-radius` will ever produce a neck.
 *
 * The rim is the field's crossing band rather than an outline of the shape,
 * which is why it thickens where two blobs are about to meet — exactly
 * where the eye is already looking.
 */

import * as React from 'react'

import { ShaderSurface } from './shader-surface'
import type { GLProgram } from './shader-runtime'

export const METABALL_LAVA_PROGRAM: GLProgram = {
  id: 'metaball-lava',
  kind: 'webgl',
  speed: 1,
  light: { c0: '#fb923c', c1: '#f43f5e', c2: '#fde68a', bg: '#fff7ed' },
  dark: { c0: '#ea580c', c1: '#9f1239', c2: '#fcd34d', bg: '#120508' },
  fragment: `
vec4 render(vec2 uv) {
  vec2 p = aspect(uv);
  float t = u_time * 0.32;

  float f = 0.0;
  for (int i = 0; i < 6; i++) {
    float fi = float(i);
    // Two incommensurate frequencies per ball, so the set never returns to
    // a formation it has already been in.
    vec2 c = vec2(
      cos(t * (0.50 + fi * 0.13) + fi * 2.1) * (0.62 + 0.18 * hash11(fi)),
      sin(t * (0.43 + fi * 0.11) + fi * 1.3) * 0.58
    );
    float r = 0.19 + 0.11 * hash11(fi + 9.0);
    vec2 d = p - c;
    f += r * r / max(dot(d, d), 1e-4);
  }

  float body = smoothstep(0.92, 1.30, f);
  float rim = smoothstep(0.88, 1.02, f) - smoothstep(1.10, 1.75, f);

  vec3 col = mix(u_bg, mix(u_c0, u_c1, clamp(f * 0.30, 0.0, 1.0)), body);
  col += u_c2 * rim * 0.95;

  return vec4(col, 1.0);
}
`,
}

export function MetaballLava({ className }: { className?: string }) {
  return <ShaderSurface program={METABALL_LAVA_PROGRAM} className={className} />
}

What's included

  • The markup and the stylesheet, in whichever framework you pick
  • No runtime dependencies

Works with

  • React
  • Vue
  • Svelte
  • Tailwind
  • styled-components
  • Plain CSS

npx hoverlab add metaball-lava

Or over MCP, from your editor's agent — no account needed.

License

Free to read, copy and install, for personal and non-commercial projects. Shipping it in client work or a paid product needs Pro ($79 once). The source lands in your repo and stops being ours — no attribution, nothing to upgrade.

Was this useful?

Motion safety

Not profiled. Canvas / WebGL effect: its deliverable is a shader, and the motion profile only reads CSS.

46 more effects in Backgrounds

All of them free to read, copy and install — no account, no locked tiles, no watermarked preview. The whole catalog is open, and so are the API and the CLI.

Browse Backgrounds

Shipping one commercially

Copying the code is free. Putting it in client work or a paid product is what Pro is for — the licence, not the access.

  • A commercial licence for everything in the catalog
  • Unlimited bundle exports, in Vue, Svelte and Tailwind
  • One payment — no subscription, nothing to renew
Pro — $79 once