Why I Built This

Not because the world needed another toast library. Because the existing ones made me unreasonably angry.

Sonner's good enough problem

Emil Kowalski built something great. Sonner has 12,000 stars, millions of weekly downloads, and a spot in shadcn's official component list. It works out of the box.

“Works out of the box” becomes “impossible to change” when you actually try to customise it. Override a default style? Need !important. Use it with Tailwind v4? Broken. Avoid memory leaks in a long-running app? Good luck — dismissed toasts are never evicted from the internal array. The DOM nodes leak too.

Emil's busy. PRs sit. Issues pile up. 60+ open at last count. The codebase relies on a singleton Observer class with setTimeout + flushSync workarounds documented as “temp solutions.” Not a dig — it's what happens when a side project gets 12,000 stars and real life doesn't pause.

The issues that convinced me

These are all real Sonner GitHub issues. All still open.

#729
Memory leakDismissed toasts never evicted from internal array. DOM nodes leaked.
#633
Styles gated behind data-styledCritical behaviour hidden behind data-styled="true" selector. Custom styles require !important.
#719
Height not recalculatedReplacing a toast with different content length — size doesn't update. Rendering bugs.
#376
Custom mobile breakpointHardcoded 600px. Open since March 2024. 7 upvotes.
#401
Loading state persistsRe-call toast() with same ID. Loading spinner stays. 10 comments, still open.
#528
Hydration error<section> rendered as child of <html>. React SSR mismatch.
#705
Hover-to-show close buttonHover-to-show was removed. Users want it back. Still open.
#714
No stable test selectorsdata-testid requires per-toast config. No default attributes.

Base UI changed the equation

In late 2025, the team behind Radix, Floating UI, and MUI shipped @base-ui/react v1.0 with proper toast primitives. Not a styling library. Not a component kit. Headless primitives that do the hard part and get out of your way.

createToastManager() — a singleton with a reactive store, proper cleanup on close, Floating UI positioning built in, ARIA live regions, F6 keyboard navigation. The foundation was there. Someone just needed to wrap it in a developer-friendly API.

What I built

A thin layer on top of Base UI Toast. If you know Sonner, you know Popser. Same API, different engine.

Sonner-compatible APItoast.success(), toast.error(), toast.promise(). Same muscle memory, different engine.
No !importantHeadless primitives. Your CSS wins every argument. Always.
Zero memory leaksBase UI's reactive store cleans up on close. No growing arrays. No leaked DOM.
Anchored toastsPin to elements, mouse events, or coordinates. Sonner can't do this.
Built-in icons5 inline SVGs + CSS spinner. Zero icon library tax.
Opt-in CSSOKLCH tokens, light/dark, rich colours. Import it or don't. Your call.
12 classNames slotsEvery toast element is targetable. Sonner only targets a handful.
E2E readydata-popser-* on every element. Zero config. Your tests stop being flaky.

The numbers

Less than half the size. And the CSS is a separate opt-in file you control.

PopserSonner
JS (gzipped)5.6 KB~13.5 KB
CSS (gzipped)2.7 KBIncluded in JS import
Total8.3 KB~17 KB
Memory leaksNone#729, #605
!important requiredNeverFor any override
Open issuesFew60+
Anchored toastsFull Floating UINone
update() APIPartial updatesRe-call with same ID
Close button modesalways / hover / neverOn or off

Migration

95% of call sites need zero changes. The remaining 5% is mechanical rename.

- import { toast } from "sonner";
+ import { toast } from "@vcui/popser";

// toast.dismiss() still works (it's an alias for toast.close)

- toast("msg", { duration: 3000 });
+ toast("msg", { timeout: 3000 });
// duration also works as an alias

What this isn't

This isn't a crusade against Sonner. Emil built a great library. It's popular for good reasons. But software ages, priorities shift, and sometimes you build the thing that exists in the gap between “good enough” and “actually good.”

I needed toast notifications built on proper primitives, with clean CSS architecture, zero memory leaks, and anchored positioning. So I built it.