Morphing Blob Toast Component for React – goey-toast

Description:

goey-toast is a React toast notification component that animates each toast through a pill-to-blob-to-pill morph cycle using Framer Motion. It wraps Sonner’s toast API with a custom animation layer, so you get the familiar goeyToast.success() / goeyToast.error() call pattern plus a configurable spring physics system underneath.

The component comes with five named toast types (default, success, error, warning, info), a promise helper that transitions through loading and resolved states, and an update method that rewrites an existing toast in-place. Every animation parameter, bounce intensity, preset, timing, spring on/off, is configurable both per-toast and globally via the <GoeyToaster /> component.

Features

  • 🧬 Blob Morph Animation: Each toast cycles through pill β†’ blob β†’ pill with SVG path animation and configurable spring physics.
  • 🎨 Five Toast Types: default, success, error, warning, and info, each with its own fill color scheme.
  • ⏳ Promise Toasts: goeyToast.promise() transitions a single toast through loading, success, and error phases automatically.
  • ✏️ In-Place Updates: goeyToast.update(id, options) rewrites title, description, type, action, or icon on an existing toast without re-creating it.
  • πŸ—‘οΈ Type-Filtered Dismiss: goeyToast.dismiss({ type: 'error' }) dismisses all toasts of one or more types at once.
  • 🧱 React Node Descriptions: The description prop accepts strings or full React components, so rich content layouts work inside the toast body.
  • πŸ•ΉοΈ Action Buttons with Success Label: An action button can morph back to pill state after click and display a successLabel string.
  • 🌊 Animation Presets: Four built-in presets β€” smooth, bouncy, subtle, snappy.
  • 🎚️ Bounce Intensity Control: A single bounce number from 0.05 to 0.8 adjusts spring stiffness, damping, and squish magnitude together.
  • πŸ“Š Countdown Progress Bar: showProgress renders a progress bar that pauses on hover and resets on re-expand.
  • ⏸️ Hover Pause and Re-Expand: Hovering an expanded toast pauses the dismiss timer. Hovering a collapsed pill re-expands it.
  • ⌨️ Keyboard and Swipe Dismiss: Escape key dismisses the most recent toast. Swipe-to-dismiss works on mobile.
  • 🌐 6 Positions with Auto-Mirroring: Right-side positions automatically mirror the morph animation horizontally. Center positions use symmetric morph.
  • πŸŒ™ Dark Mode and RTL: theme="dark" and dir="rtl" props handle both display variants.
  • πŸ—‚οΈ Toast Queue: maxQueue and queueOverflow control what happens when the toast stack fills up.
  • 🎭 CSS Class Overrides: The classNames prop targets wrapper, content, header, title, icon, description, actionWrapper, and actionButton independently.
  • πŸ”” Dismiss Callbacks: onDismiss fires on any dismiss; onAutoClose fires only on timer expiry.
  • ⏱️ Timestamp Display: Expanded toasts show a timestamp.
  • πŸ“± shadcn/ui Compatible: A one-command shadcn CLI install path drops a wrapper component into components/ui/.

Preview

morphing-blob-goey-toast

How to Use It

Installation

Install the package from npm.

npm install goey-toast

goey-toast requires React 18+, react-dom 18+, and framer-motion 10+. Install peer dependencies if your project does not already include them.

npm install react react-dom framer-motion

shadcn/ui Installation

Run the shadcn CLI add command to install a pre-configured wrapper component at components/ui/goey-toaster.tsx. This command also auto-installs goey-toast and framer-motion.

npx shadcn@latest add https://goey-toast.vercel.app/r/goey-toaster.json

CSS Import (Required)

Import the goey-toast stylesheet once in your app entry point. Without this import, toasts render without any visual styling.

import 'goey-toast/styles.css'

Add this line to main.tsx or App.tsx.

Quick Start

Place <GoeyToaster /> once in your app root and call goeyToast methods from anywhere.

import { GoeyToaster, goeyToast } from 'goey-toast'
import 'goey-toast/styles.css'
function App() {
  return (
    <>
      <GoeyToaster position="bottom-right" />
      <button onClick={() => goeyToast.success('Saved!')}>Save</button>
    </>
  )
}

Calling Toast Methods

goeyToast('This is a default toast')
goeyToast.success('File uploaded')
goeyToast.error('Upload failed')
goeyToast.warning('Storage almost full')
goeyToast.info('New version available')

Adding a Description

Pass a description string as part of the options object.

goeyToast.error('Payment failed', {
  description: 'Your card was declined. Please try another payment method.',
})

Pass a React component as description for richer layouts.

goeyToast.success('Deployment complete', {
  description: (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
      <div><span>Environment:</span> <strong>Production</strong></div>
      <div><span>Branch:</span> <strong>main @ 3f8a2c1</strong></div>
    </div>
  ),
})

Action Button with Success Label

After the user clicks the action button, the toast morphs back to the pill shape and shows successLabel.

goeyToast.info('Share link ready', {
  description: 'Your link has been generated.',
  action: {
    label: 'Copy to Clipboard',
    onClick: () => navigator.clipboard.writeText(url),
    successLabel: 'Copied!',
  },
})

Promise Toast

goeyToast.promise() tracks a Promise and transitions the toast through loading, success, and error states automatically.

goeyToast.promise(saveData(), {
  loading: 'Saving...',
  success: 'Changes saved',
  error: 'Something went wrong',
  description: {
    success: 'All changes have been synced to the server.',
    error: 'Please try again later.',
  },
  action: {
    error: {
      label: 'Retry',
      onClick: () => retryOperation(),
    },
  },
})

success and error also accept functions that receive the resolved value or error object.

goeyToast.promise(fetchUser(id), {
  loading: 'Loading user...',
  success: (data) => `Welcome back, ${data.name}`,
  error: (err) => `Failed: ${err.message}`,
})

Updating a Toast In-Place

Call goeyToast.update(id, options) to rewrite an active toast without removing and recreating it. Store the id returned from the initial call.

const id = goeyToast('Uploading...', {
  icon: <SpinnerIcon />,
})
// After upload resolves:
goeyToast.update(id, {
  title: 'Upload complete',
  type: 'success',
  description: '3 files uploaded.',
  icon: null,
})

Pass icon: null to clear a custom icon that was set on the original toast.

Dismissing Toasts

// Dismiss a specific toast by id
goeyToast.dismiss(toastId)
// Dismiss all toasts of one type
goeyToast.dismiss({ type: 'error' })
// Dismiss multiple types at once
goeyToast.dismiss({ type: ['error', 'warning'] })
// Dismiss every active toast
goeyToast.dismiss()

Custom Styling

Pass fillColor, borderColor, and borderWidth directly in the options object for per-toast color overrides.

goeyToast.success('Custom styled toast', {
  fillColor: '#1a1a2e',
  borderColor: '#6c63ff',
  borderWidth: 2,
  classNames: {
    wrapper: 'my-toast-wrapper',
    title: 'my-toast-title',
    description: 'my-toast-desc',
    actionButton: 'my-toast-btn',
  },
})

Controlling Display Duration

Set displayDuration in the timing object to override how long the toast stays expanded before auto-dismissing.

goeyToast.success('Saved', {
  description: 'Your changes have been synced.',
  timing: { displayDuration: 5000 },
})

Spring Animations

Spring animations are on by default. Disable them per-toast or globally.

// Per-toast
goeyToast.success('Saved', { spring: false })
// Globally via GoeyToaster
<GoeyToaster spring={false} />

When spring is false, all spring-based animations (landing squish, blob squish, morph transitions, pill resize, header squish) switch to smooth ease-in-out curves. Error shake animations still fire regardless.

Bounce Intensity

The bounce value from 0.05 to 0.8 adjusts spring stiffness, damping, and squish magnitude as a single unit.

// Minimal spring feel
goeyToast.success('Saved', { bounce: 0.1 })
// Default
goeyToast.success('Saved', { bounce: 0.4 })
// Exaggerated spring
goeyToast.success('Saved', { bounce: 0.8 })
// Set globally
<GoeyToaster bounce={0.6} />

Animation Presets

Four built-in presets handle common animation personalities. Apply per-toast or globally.

goeyToast.success('Deployed', { preset: 'bouncy' })
<GoeyToaster preset="smooth" />

Available preset names: smooth, bouncy, subtle, snappy.

Countdown Progress Bar

Show a progress bar that depletes as the dismiss timer counts down. It pauses on hover and resets on re-expand.

goeyToast.success('Saved', { showProgress: true })
// Or enable globally
<GoeyToaster showProgress />

Dark Mode

<GoeyToaster theme="dark" />

RTL Layout

<GoeyToaster dir="rtl" />

Keyboard and Swipe Dismiss

Keyboard dismiss (Escape) and swipe-to-dismiss on mobile are both enabled by default. Disable either individually.

<GoeyToaster closeOnEscape={false} swipeToDismiss={false} />

Toast Queue

maxQueue limits how many toasts can stack. queueOverflow controls which toast to drop when the limit is reached.

<GoeyToaster maxQueue={5} queueOverflow="drop-oldest" />

Dismiss Callbacks

onDismiss fires whenever a toast is dismissed for any reason. onAutoClose fires only when the timer expires.

goeyToast.success('Saved', {
  onDismiss: (id) => console.log(`Toast ${id} was dismissed`),
  onAutoClose: (id) => console.log(`Toast ${id} auto-closed`),
})

API Reference

goeyToast Methods

MethodDescription
goeyToast(title, options?)Default (neutral) toast
goeyToast.success(title, options?)Green success toast
goeyToast.error(title, options?)Red error toast
goeyToast.warning(title, options?)Yellow warning toast
goeyToast.info(title, options?)Blue info toast
goeyToast.promise(promise, data)Tracks a Promise through loading β†’ success/error
goeyToast.update(id, options)Rewrites an active toast in-place
goeyToast.dismiss(idOrFilter?)Dismisses one toast, all of a type, or all toasts

GoeyToastOptions

OptionTypeDescription
descriptionReactNodeBody content (string or component)
actionGoeyToastActionAction button config
iconReactNodeCustom icon override
durationnumberDisplay duration in ms
idstring | numberUnique toast identifier
classNamesGoeyToastClassNamesCSS class overrides per element
fillColorstringBackground color of the blob
borderColorstringBorder color of the blob
borderWidthnumberBorder width in px (default 1.5)
timingGoeyToastTimingsAnimation timing overrides
springbooleanEnable spring/bounce animations (default true)
bouncenumberSpring intensity from 0.05 to 0.8 (default 0.4)
showProgressbooleanShow countdown progress bar
onDismiss(id) => voidCalled on any dismiss
onAutoClose(id) => voidCalled only on timer-based auto-dismiss
presetAnimationPresetName'smooth', 'bouncy', 'subtle', or 'snappy'

GoeyToastUpdateOptions

OptionTypeDescription
titlestringNew title text
descriptionReactNodeNew body content
typeGoeyToastTypeChange the toast type and color
actionGoeyToastActionNew action button
iconReactNode | nullCustom icon (pass null to clear)

GoeyToastAction

PropertyTypeRequiredDescription
labelstringYesButton text
onClick() => voidYesClick handler
successLabelstringNoLabel shown after click with morph-back animation

GoeyToastTimings

PropertyTypeDefaultDescription
displayDurationnumber4000Milliseconds the toast stays expanded

GoeyToastClassNames

KeyTarget element
wrapperOuter container
contentContent area
headerIcon and title row
titleTitle text
iconIcon wrapper
descriptionBody text
actionWrapperButton container
actionButtonAction button

GoeyToasterProps

PropTypeDefaultDescription
position'top-left' | 'top-center' | 'top-right' | 'bottom-left' | 'bottom-center' | 'bottom-right''bottom-right'Toast stack position
durationnumberβ€”Default display duration in ms
gapnumber14Gap between stacked toasts (px)
offsetnumber | string'24px'Distance from screen edge
theme'light' | 'dark''light'Color theme
toastOptionsPartial<ExternalToast>β€”Default options passed to Sonner
springbooleantrueEnable spring/bounce globally
bouncenumber0.4Spring intensity (0.05 to 0.8)
presetAnimationPresetNameβ€”Animation preset for all toasts
closeOnEscapebooleantrueDismiss most recent toast on Escape
showProgressbooleanfalseShow countdown progress bar globally
maxQueuenumberInfinityMaximum toasts in queue
queueOverflow'drop-oldest' | 'drop-newest''drop-oldest'Overflow strategy
dir'ltr' | 'rtl''ltr'Layout direction
swipeToDismissbooleantrueEnable swipe-to-dismiss on mobile

GoeyPromiseData<T>

PropertyTypeRequiredDescription
loadingstringYesTitle shown during loading
successstring | ((data: T) => string)YesTitle on success
errorstring | ((error: unknown) => string)YesTitle on error
description.loadingReactNodeNoBody content during loading
description.successReactNode | ((data: T) => ReactNode)NoBody content on success
description.errorReactNode | ((error: unknown) => ReactNode)NoBody content on error
action.successGoeyToastActionNoAction button on success state
action.errorGoeyToastActionNoAction button on error state
classNamesGoeyToastClassNamesNoCSS class overrides
fillColorstringNoBackground color
borderColorstringNoBorder color
borderWidthnumberNoBorder width in px
timingGoeyToastTimingsNoAnimation timing overrides
springbooleanNoEnable spring animations (default true)
bouncenumberNoSpring intensity (default 0.4)
onDismiss(id) => voidNoCalled on any dismiss
onAutoClose(id) => voidNoCalled only on timer-based auto-dismiss

Package Exports

// Components
export { GoeyToaster } from 'goey-toast'
// Toast function
export { goeyToast } from 'goey-toast'
// Animation presets
export { animationPresets } from 'goey-toast'
// Types
export type {
  GoeyToastOptions,
  GoeyPromiseData,
  GoeyToasterProps,
  GoeyToastAction,
  GoeyToastClassNames,
  GoeyToastTimings,
  GoeyToastUpdateOptions,
  DismissFilter,
  AnimationPreset,
  AnimationPresetName,
} from 'goey-toast'

Related Resources

  • Sonner: The lightweight toast library that goey-toast builds on top of for queue and lifecycle management.
  • Framer Motion: The animation library that drives the spring physics and SVG morph sequences in goey-toast.
  • shadcn/ui: The component distribution system with a CLI path that goey-toast supports for drop-in installation.
  • React Hot Toast: An alternative lightweight toast library worth comparing for projects that do not need morphing animations.

FAQs

Q: Do I need to install Sonner separately?
A: No. goey-toast bundles its own Sonner dependency.

Q: Can I use goey-toast with Next.js App Router?
A: Yes. Place <GoeyToaster /> inside a Client Component (mark it with "use client") and render it in your root layout. Call goeyToast methods from any Client Component in the tree.

Q: What happens when goeyToast.update() targets a toast that has already been dismissed?
A: The update call has no effect. The toast is no longer in the active queue, so there is no element to update. Store the id only for the window of time when the toast is active.

Q: Can bounce and preset be set at the same time?
A: Yes, but the preset takes effect first as a baseline. If you also pass bounce, the bounce value overrides the spring intensity from the preset while leaving the other preset timing values intact.

Q: Does the progress bar pause when the toast collapses to a pill?
A: The progress bar pauses on hover. When the dismiss timer fires and the toast begins pre-dismiss collapse, the bar finishes and the toast auto-dismisses on schedule. Re-expanding the collapsed pill via hover pauses the timer and the bar together.

Tags:

Add Comment