# goey-toast adds a blob morph to Sonner and keeps the queue

> goey-toast is a toast component built on Sonner with Framer Motion animations, where an expanded pill collapses into a blob and back on hover, with an API for in-place updates and type-filtered dismissal. The install has one hard requirement that the README flags twice: you must import its stylesheet or toasts render unstyled.

**anl331/goey-toast** — A gooey, morphing toast component built on Sonner with Framer Motion animations

- Repository: https://github.com/anl331/goey-toast
- Website: https://goey-toast.vercel.app
- Stars: 1,340 · Forks: 60
- Language: TypeScript
- License: MIT
- Published: 2026-10-17 · Updated: 2026-10-17 · Language: en
- Canonical page: https://hysenlabs.com/projects/anl331-goey-toast

## Sonner underneath, one real dependency

The package description says it plainly: a gooey, morphing toast component built on Sonner with Framer Motion animations. The project is built on Sonner and adds its own visual treatment, options and update and dismissal APIs.

The dependency list confirms how thin it is. `sonner` at `^2.0.7` is the only runtime dependency. Everything else is a peer dependency, declared as React and React DOM at `>=18.0.0` and Framer Motion at `>=10.0.0`, so the animation library is not bundled twice into an application that already uses it.

The morph itself has three states. An expanded toast is a pill, and dismissing runs a collapse animation first. Hovering over a collapsed pill re-expands it, so a user who reaches for a toast does not lose the content while doing so.

That interaction has a companion behaviour that most toast libraries get wrong: hovering an expanded toast pauses the dismiss timer. Without it, a toast disappears under the pointer, which is a small annoyance that becomes a complaint.

The position handling is unusually thorough. There are six positions, right-side positions mirror horizontally so the blob animates in the natural direction, and centre positions get a symmetric morph rather than the asymmetric one.

## The stylesheet import is not optional

This is the one installation detail that will cost you time if you miss it, and the README states it in bold terms: you must import the goey-toast stylesheet for the component to render correctly.

The import goes once in your application entry point, such as `main.tsx` or `App.tsx`:

```tsx
import 'goey-toast/styles.css'
```

Without it, toasts appear unstyled. That is a harsh failure mode for a visual component, since nothing errors and the element is still there.

The packaging supports it properly rather than leaving you to guess. The `exports` map in package.json has a dedicated entry mapping `./styles.css` to `./dist/index.css`, alongside the main entry with types, import and require conditions for both ESM and CommonJS. And `sideEffects` is set to `["*.css"]`, which tells a bundler's tree shaking that the CSS import must be preserved.

That is the difference between a component that needs an unusual loader configuration and one that works in a default setup, and it is handled.

The rest of the install is ordinary. The package itself comes from npm, and the peer dependencies need to be present:

```bash
npm install react react-dom framer-motion
```

## The API is a small object plus five type methods

The calling convention is a function with a type shortcut for each variant. `gooeyToast(title, options?)` is the neutral default, and `.success`, `.error`, `.warning` and `.info` map to green, red, yellow and blue respectively.

Three methods carry the weight.

`gooeyToast.promise(promise, data)` handles the async case, showing loading and transitioning to success or error.

`gooeyToast.update(id, options)` changes an existing toast in place rather than removing and re-creating it. The update options are `title` as a string, `description` as a React node, `type`, `action`, and `icon`, where passing `null` clears a custom icon.

`gooeyToast.dismiss(idOrFilter?)` is the most interesting, because it accepts either an id, a type filter, an array of types, or nothing. So dismissing every error toast at once is a single call:

```ts
// Dismiss all error toasts
gooeyToast.dismiss({ type: 'error' })

// Dismiss multiple types
gooeyToast.dismiss({ type: ['error', 'warning'] })

// Dismiss all toasts
gooeyToast.dismiss()
```

That is a small API decision with real consequences. A failed upload followed by a validation error should be able to clear all failures at once without clearing unrelated informational toasts, and a per-id-only API forces you to track ids you did not ask for.

The dismissal callbacks are also split, which is the right split: `onDismiss` fires when a toast is dismissed for any reason, while `onAutoClose` fires only on timer-based auto-dismiss.

## Tuning the animation, and why bounce is a number

The options table exposes the animation as ordinary configuration, which is where this differs from a component that ships one look.

`duration` sets display time in milliseconds. `spring` toggles spring behaviour and defaults to true. `bounce` is a number from 0.05, described as subtle, through 0.8, described as dramatic, with a default of 0.4. `preset` accepts one of four named presets: smooth, bouncy, subtle and snappy.

So there are two ways to set the same dimension, a named preset or raw numbers, and they overlap. That is a mild API wart, but a defensible one: presets for consistency, numbers for when a designer asks for something specific.

The visual options are equally exposed. `fillColor` sets the blob background, `borderColor` the border, `borderWidth` in pixels with a default of 1.5, and `classNames` accepts CSS class overrides when you need to escape the component's own styling.

Other behaviour worth knowing: `showProgress` draws a countdown progress bar that pauses on hover and re-expands; `showTimestamp` displays a timestamp in the header or body and defaults to true; keyboard dismissal works with Escape; swipe-to-dismiss is supported on mobile; descriptions accept a string or a React component, so a toast can carry structured content rather than only text.

There is dark mode support and RTL layout support, and a toast queue with a configurable overflow strategy.

Action buttons can morph back with an optional success label, which is a small detail that shows the animation system is used consistently rather than only on the container.

## Distribution: shadcn, an agent skill, and a demo app

The package ships in three ways, and the second and third are unusual.

The first is the shadcn/ui registry route, which installs a thin wrapper component:

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

That writes `components/ui/goey-toaster.tsx` and auto-installs the goey-toast and framer-motion packages.

The second is an Agent Skill. The README states the package ships a bundled `SKILL.md` so coding agents such as Claude Code and Cursor know how to install and use it correctly, specifically the required `<GooeyToaster />` mount, the `styles.css` import, and the full API. That is an admission that agents routinely get this wrong, and a sensible response to it.

The skill installs from the skills.sh registry:

```bash
npx skills add anl331/goey-toast
```

Or from the package itself, into `.claude/skills/goey-toast/SKILL.md`, with an option to append a pointer to AGENTS.md and to target another directory such as `.cursor/skills/goey-toast`.

The third is the demo: a `demo/` directory with its own Vite configuration and tsconfig files, deployed at goey-toast.vercel.app, which serves as both the live demo and the documentation.

The minimum setup is deliberately small:

```tsx
import { GooeyToaster, gooeyToast } from 'goey-toast'
import 'goey-toast/styles.css'
```

One component to mount and a call to make a toast.

## Build setup and where the project stands

The package version in package.json is 0.5.0, so this is a pre-1.0 component. The `engines` field requires Node 18 or newer.

The build uses tsup, which produces the dual ESM and CommonJS output the exports map expects. Tests run on Vitest with jsdom and Testing Library, and typecheck is `tsc --noEmit`. There is a `bin` entry pointing at `bin/cli.mjs`, which is the script behind the `add-skill` commands, and the `files` array ships `dist`, `skills` and `bin`.

There are no GitHub releases, so there is no tag history to consult and no version stream other than the npm package. The last push was on 2026-09-05 and the repository is not archived, with a `CHANGELOG.md` at the root as the version history within the repository.

For a component at 0.5.0 with a large option surface, the practical advice is to pin an exact version rather than a range, and to read the CHANGELOG before upgrading, since an API this configurable can change defaults without a major bump.

The licence is MIT.

## Conclusion

Adopt goey-toast if you already use Sonner or want its toast queue with a heavier visual treatment, and you can accept depending on Framer Motion for the animation. Do not adopt it for an interface that must be accessible without motion, since the visual identity is the animation, and check your reduced-motion story first. Verify that you import 'goey-toast/styles.css' exactly once in your entry point, that React 18 or newer and Framer Motion 10 or newer are present as peers, and pin a specific version since the package is at 0.5.0 with no GitHub releases.

## FAQ

### How do I install goey-toast?

With `npm install goey-toast`, plus React and React DOM at 18 or newer and Framer Motion at 10 or newer as peers. There is also a shadcn registry command, `npx shadcn@latest add https://goey-toast.vercel.app/r/goey-toaster.json`, which installs a wrapper component and the two packages.

### Why do my goey-toast notifications appear unstyled?

The stylesheet import is required. The README says you must add `import 'goey-toast/styles.css'` once in your entry point, and that without it toasts will appear unstyled.

### How do I dismiss all error toasts in goey-toast?

Pass a type filter: `gooeyToast.dismiss({ type: 'error' })`. You can also pass an array such as `{ type: ['error', 'warning'] }`, a single toast id, or nothing at all to dismiss everything.

### How do I control how the goey-toast animation feels?

With the `preset` option for a named feel such as smooth, bouncy, subtle or snappy, or numerically with `bounce`, which ranges from 0.05 for subtle to 0.8 for dramatic with a default of 0.4, plus `spring` to toggle spring behaviour and `duration` for display time in milliseconds.

## Sources

- [anl331/goey-toast on GitHub](https://github.com/anl331/goey-toast)
- [Issues](https://github.com/anl331/goey-toast/issues)
- [License: MIT](https://github.com/anl331/goey-toast/blob/main/LICENSE)
- [Project website](https://goey-toast.vercel.app)
- [README](https://github.com/anl331/goey-toast/blob/main/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/anl331-goey-toast
