Gooey SVG Filter

An SVG filter that creates a gooey, fluid merging effect on adjacent elements using Gaussian blur and alpha-channel color matrix manipulation.

The Gooey SVG Filter renders an invisible <svg> element with a declarative filter definition. Apply it to any container via style={{ filter: "url(#your-id)" }} to make child elements fluidly merge together as they move — ideal for pill selectors, blob menus, expandable action bars, and any UI where motion should feel liquid.

The effect works in two steps: a feGaussianBlur makes nearby elements bleed into each other, then a feColorMatrix cranks up the alpha contrast to snap that blur back into sharp, merged shapes.

Limited browser support. The feColorMatrix alpha trick used here is not supported in Safari or Firefox. In those browsers the filter renders as a plain Gaussian blur without the merging effect. Always test in Chrome/Edge and provide a graceful fallback for other browsers — for example by disabling the filter entirely when the user is on Safari or Firefox.

Installation

CLI installation is not available for Pro components.

File Structure

gooey-svg-filter.tsx

Demo

The demo file is not included by default. But the animation is pretty cool huh? Download it here:

Usage

import GooeySvgFilter from "@/components/unlumen-ui/gooey-svg-filter";

export default function Example() {
  return (
    <div className="relative">
      {/* Render the filter once anywhere in the tree — it's invisible */}
      <GooeySvgFilter id="gooey-filter" strength={15} />

      {/* Apply it to the container that holds the animating blobs */}
      <div style={{ filter: "url(#gooey-filter)" }}>
        <div className="w-12 h-12 rounded-full bg-black" />
        <div className="w-12 h-12 rounded-full bg-black" />
      </div>
    </div>
  );
}

With a Safari fallback

Detect the browser and conditionally disable the filter on unsupported engines:

import { useEffect, useState } from "react";
import GooeySvgFilter from "@/components/unlumen-ui/gooey-svg-filter";

function useIsSafari() {
  const [isSafari, setIsSafari] = useState(false);
  useEffect(() => {
    setIsSafari(/^((?!chrome|android).)*safari/i.test(navigator.userAgent));
  }, []);
  return isSafari;
}

export default function Example() {
  const isSafari = useIsSafari();

  return (
    <div className="relative">
      <GooeySvgFilter id="gooey-filter" strength={15} />
      <div style={{ filter: isSafari ? "none" : "url(#gooey-filter)" }}>
        {/* your blobs */}
      </div>
    </div>
  );
}

The two-layer pattern

Because the gooey filter blurs and recomposites everything inside the container, text and interactive elements placed inside it will look distorted. The standard solution is to split your UI into two absolutely-positioned layers:

<div className="relative">
  {/* Layer 1 — filtered: contains only the background blobs */}
  <div className="absolute inset-0" style={{ filter: "url(#gooey-filter)" }}>
    <motion.div layoutId="pill" className="absolute ... rounded-full bg-black" />
  </div>

  {/* Layer 2 — no filter: contains buttons and text, perfectly crisp */}
  <div className="relative flex">
    {options.map((opt) => (
      <button key={opt} onClick={...}>{opt}</button>
    ))}
  </div>
</div>

API Reference

id?
string
"gooey-filter"

The filter identifier. Must match the `url(#id)` value used in `style.filter` on your container.

strength?
number
15

Controls the intensity of the gooey effect. Maps to the `stdDeviation` of the internal Gaussian blur. Higher values produce a stronger, more exaggerated merge. Reduce on small screens to prevent clipping.

Credits

Technique originally described by Lucas Bebber in The Gooey Effect on CSS-Tricks. React component adapted from Fancy Components by Daniel Petho.

Keep in mind

Most components on this site are inspired by or recreated from existing work across the web. I'm not here to take credit; just to learn, experiment, and sometimes push things a bit further. If something looks familiar and I forgot to mention you, reach out and I'll fix that right away.