npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

blobby-modal

v1.0.1

Published

React modal with gooey blob animations and morph transitions

Readme

blobby-modal

A React modal library with gooey blob animations, morph transitions, and asymmetric squish effects. Built with Framer Motion.

Live Demo

Features

  • Gooey blob animation — SVG filter creates organic, liquid blob edges during open/close
  • Morph transition — modal expands directly from any trigger element and collapses back
  • Asymmetric squish — spring-driven scaleX/scaleY deformation for a realistic blob feel
  • Fully responsive — adapts to any screen size with automatic width clamping
  • Customizable springs — tune stiffness, damping, and mass or disable springs entirely
  • Backdrop blur — optional frosted glass effect behind the modal
  • Live updates — update modal content while it's open
  • TypeScript — full type definitions included
  • Lightweight — ~14 kB published, zero runtime dependencies beyond peer deps

Installation

npm install blobby-modal

Peer dependencies (must be installed separately):

npm install react react-dom framer-motion

Requires React 18+ and Framer Motion 11+.

Quick Start

Wrap your app with ModalProvider, then use useModal anywhere inside it.

import { ModalProvider, useModal } from 'blobby-modal'

function App() {
  return (
    <ModalProvider>
      <MyPage />
    </ModalProvider>
  )
}

function MyPage() {
  const modal = useModal()

  return (
    <button onClick={() => modal.open({
      title: 'Hello!',
      children: <p>This is a blobby modal.</p>,
    })}>
      Open Modal
    </button>
  )
}

Morph Transition

Use useMorphTrigger to make the modal expand from a specific element and collapse back into it.

import { useMorphTrigger } from 'blobby-modal'

function MyButton() {
  const morph = useMorphTrigger<HTMLButtonElement>()

  return (
    <button
      ref={morph.ref}
      onClick={() => morph.openModal({
        title: 'Morphed!',
        children: <p>This modal expanded from the button.</p>,
      })}
    >
      Click me
    </button>
  )
}

The modal captures the trigger element's position, size, border radius, and background color, then animates a smooth spring transition between the two states.

API

useModal()

Returns an object with:

| Method | Description | |--------|-------------| | open(options) | Open a modal with the given options | | close() | Close the current modal | | update(options) | Update the open modal's options (partial) | | isOpen | boolean — whether a modal is currently open |

useMorphTrigger<T>()

Returns an object with:

| Property | Description | |----------|-------------| | ref | Attach to the trigger element (ref={morph.ref}) | | openModal(options) | Open a modal that morphs from the trigger element |

ModalOptions

All options you can pass to open() or openModal():

Content

| Prop | Type | Default | Description | |------|------|---------|-------------| | title | string | — | Modal heading text | | children | ReactNode | — | Body content |

Buttons

| Prop | Type | Default | Description | |------|------|---------|-------------| | primaryLabel | string | "OK" | Primary button label | | onPrimary | () => void | — | Primary button callback | | secondaryLabel | string | — | Secondary button label (shows button when set) | | onSecondary | () => void | — | Secondary button callback | | onClose | () => void | — | Called on dismiss (close button, ESC, backdrop click) |

Layout

| Prop | Type | Default | Description | |------|------|---------|-------------| | width | number | 480 | Modal width in px (auto-clamped to viewport) | | borderRadius | number | 20 | Corner radius in px |

Colors

| Prop | Type | Default | Description | |------|------|---------|-------------| | backgroundColor | string | "#ffffff" | Modal background | | titleColor | string | "#111111" | Title text color | | bodyColor | string | "#555555" | Body text color | | primaryButtonBg | string | titleColor | Primary button background | | primaryButtonTextColor | string | backgroundColor | Primary button text |

Animation

| Prop | Type | Default | Description | |------|------|---------|-------------| | spring | boolean \| object | true | true = default spring, false = ease curve, { stiffness, damping, mass } = custom | | gooeyBlur | number | 10 | Gooey SVG filter blur radius | | gooeyIntensity | number | 20 | Gooey SVG filter intensity |

Backdrop

| Prop | Type | Default | Description | |------|------|---------|-------------| | backdropBlur | number | 0 | Backdrop blur in px | | backdropColor | string | "rgba(0,0,0,0.4)" | Backdrop overlay color |

Behaviour

| Prop | Type | Default | Description | |------|------|---------|-------------| | showCloseButton | boolean | true | Show/hide the close button | | hideHeader | boolean | false | Hide built-in header | | hideFooter | boolean | false | Hide built-in footer |

Custom Styles

| Prop | Type | Description | |------|------|-------------| | style | CSSProperties | Custom styles on modal card | | primaryButtonStyle | CSSProperties | Custom styles on primary button | | secondaryButtonStyle | CSSProperties | Custom styles on secondary button |

Examples

Confirm Dialog

modal.open({
  title: 'Delete item?',
  children: <p>This action cannot be undone.</p>,
  primaryLabel: 'Delete',
  secondaryLabel: 'Cancel',
  primaryButtonBg: '#dc2626',
})

Custom Spring

modal.open({
  title: 'Bouncy',
  children: <p>Extra bouncy spring animation.</p>,
  spring: { stiffness: 200, damping: 10, mass: 1 },
})

Backdrop Blur

modal.open({
  title: 'Frosted',
  children: <p>Blurred backdrop behind the modal.</p>,
  backdropBlur: 8,
})

Custom Content (No Header/Footer)

modal.open({
  hideHeader: true,
  hideFooter: true,
  children: <MyCustomForm onClose={() => modal.close()} />,
})

Update While Open

modal.open({
  title: 'Loading...',
  showCloseButton: false,
})

// Later...
modal.update({
  title: 'Done!',
  showCloseButton: true,
  primaryLabel: 'Great',
})

How It Works

Gooey mode uses an SVG filter (feGaussianBlur + feColorMatrix) applied during the scale animation to create organic blob edges. The filter is active during expansion and collapse, then removed when the modal is at rest.

Morph mode captures the trigger element's bounding rect and computed styles, then animates position, size, border radius, and background color from the trigger to the modal's final state using a spring.

Squish effect adds asymmetric scaleX/scaleY deformation after the main animation completes, using Math.sin(v * Math.PI) as an intensity curve. The spring overshoot creates natural wobble.

License

MIT