Skip to content

Headless toast / notification comparison

How @rozie-ui/toast compares to the existing toast / notification libraries across the six frameworks. Like the slider, listbox, and OTP input, the toast host has no shared vanilla-JS engine, and like the OTP landscape, the toast landscape is overwhelmingly per-framework: every ecosystem grew its own notification system (sonner, react-hot-toast, react-toastify, vue-sonner, vue-toastification, ngx-toastr, …) with its own API, its own queue model, and its own theming story. Rozie delivers the same idiomatic <Toaster>, driven by an imperative ref, on all six frameworks: same props, same handle, same scoped slot, same @dismissed event, installed as a pre-compiled per-framework package with no Rozie toolchain required.

Research snapshot: 2026-08-10. The toast landscape is fragmented and fast-moving; treat the library names, framework coverage, and feature columns as of that date.

The libraries at a glance

FrameworkRepresentative option(s)ShapeNotes
Reactsonner, react-hot-toast, react-toastifyglobal toast() + mounted hostThe deepest ecosystem. sonner (the shadcn/ui default) is the polish leader — animated stack, swipe, toast.promise; react-hot-toast is the tiny headless-hooks option; react-toastify (v11) is the batteries-included veteran with drag-to-dismiss.
Vuevue-sonner, vue-toastificationglobal service / toast()vue-sonner ports Sonner (swipe + promise); vue-toastification is the configurable workhorse (pauseOnHover, draggable, custom component body).
Sveltesvelte-french-toast, @zerodevx/svelte-toaststore / toast()svelte-french-toast is a react-hot-toast port (promise, hover-pause); @zerodevx/svelte-toast is store-driven with pausable hover-pause. Its own mental model again.
Solidsolid-toastglobal toast()A react-hot-toast-style port — promise, hover-pause, custom render. The de-facto single option.
Angularngx-toastr, @angular/material snackbar (CDK overlay)injected servicengx-toastr (v20) is the standard: positions, progress bar, animations. Material's MatSnackBar shows one message at a time — a different shape, not a stack.
Lit / web components(none mainstream)No mainstream toast component to point at — you hand-roll the queue, timers, positioning, and ARIA.
Rozie@rozie-ui/toast-*a component + ref handleSame API on all six: same props, same show / dismiss / clear / patch / promise handle, same #toast scoped slot, same @dismissed event. No global singleton.

On its home framework each of these is a solid pick. The case for Rozie is consistency, coverage, and the deliberate non-singleton shape: no toast component spans every framework with the same API; each ecosystem reimplements the queue, the auto-dismiss timers, hover-pause, and positioning from scratch, and Lit / web components have nothing mainstream at all. @rozie-ui/toast gives them all the same <Toaster>.

The global-singleton question

The deepest decision in a toast library is how you call show from anywhere. Two camps:

  • A global singleton (sonner's toast(), react-hot-toast, solid-toast, vue-sonner, ngx-toastr's injected ToastrService): you mount one host once, and a free-floating toast(...) function (or an injected service) talks to it through module-global or framework-DI state. Maximally convenient to call, at the cost of hidden global state, SSR / multiple-root hazards, and a host that is hard to scope to a subtree.
  • An owned component with an imperative handle (Rozie): the <Toaster> owns its queue + timers as ordinary component state, and you reach it through your framework's native ref. "Call from anywhere" becomes an app-wiring decision you make explicitly (stash the ref in a store / context / service of your choosing) rather than a global the library imposes.

Rozie picks the owned-component camp deliberately. It is the only shape that compiles cleanly to every framework without a per-framework global-state mechanism (a React context, a Vue plugin, a Svelte store, an Angular service, …), it keeps the dependency graph clean, and it side-steps the "context doesn't cross a portal" limitation. If you want a global toast() ergonomic, it is a few lines of wrapper you own, and you keep the ability to scope multiple independent hosts to different subtrees.

Imperative-first, with one lifecycle event

Most toast libraries are imperative-first already; many also surface lifecycle callbacks (onDismiss, onAutoClose). @rozie-ui/toast stays imperative-first (the primary write surface is still the show / dismiss / clear / patch / promise handle, and the built-in close button calls dismiss internally), but it now has its one lifecycle event: @dismissed { toast, reason }, fired once per toast at dismissal initiation (clear() stays bulk and fires nothing). One event, not a callback-per-lifecycle-stage grab-bag, is what keeps the same source fully consistent. There is still no model: true prop and no Angular ControlValueAccessor (a toast host is not a form control).

Feature matrix

Cell legend: = documented out-of-the-box · = not supported / not present · ⚠️ = partial / consumer-assembly-required. The non-Rozie columns name the most representative library per framework.

CapabilityReact (sonner)Vue (vue-sonner)Svelte (french-toast)Solid (solid-toast)Angular (ngx-toastr)Lit (none)@rozie-ui/toast
Queue + auto-dismiss
Hover-to-pause timers⚠️ extendedTimeOut✅ (disablePauseOnHover opt-out; resumes from the exact remainder, not a full restart¹)
Multiple positions✅ (6 corners)
Custom toast render✅ (#toast slot)
Accessible ARIA live region✅ (role=region/status, severity aria-live)
Promise / loading toast✅ (promise() verb + 'loading' type)
Swipe-to-dismiss✅ (on by default; disableSwipe opt-out)
Animated stack / expand⚠️⚠️⚠️✅ enter/exit animations always; opt-in stacked collapsed overlay
Zero-config, fully re-skinnable theming⚠️ styled, CSS-var overrides⚠️⚠️⚠️⚠️ ships CSS✅ CSS-var tokens + shadcn / Material / Bootstrap bridges
Ref-driven (no global singleton)❌ global toast()❌ injected service✅ imperative ref handle
Same API on all 6 frameworks

¹ Hovering pauses every timer and stores its exact remainder; leaving resumes from that remainder: a 1000ms toast hovered ~600ms in and released dismisses ~400ms later, not after a fresh 1000ms.

Where Rozie wins today

  • First-class packages everywhere, including Lit / web components, which have no mainstream toast component at all, and Svelte / Solid, which lean on store / singleton patterns. All are categories the incumbents barely serve uniformly.
  • The same component surface everywhere. Where the ecosystem offers a different library per framework (six APIs, six queue models, six theming stories), @rozie-ui/toast is one <Toaster> to learn, document, and migrate: the same props (position / duration / max / disablePauseOnHover / ariaLabel / disableSwipe / stacked), the same show / dismiss / clear / patch / promise handle, the same #toast scoped slot, and the same @dismissed event.
  • No imposed global state. The host is a normal component you ref, with no module-global singleton, no plugin install, and no injected service, so it keeps the dependency graph clean, is SSR-safe (every timer is typeof window-guarded), and is scopable to any subtree.
  • Precise, gesture-rich dismissal, matched to the polish leaders. Hover-to-pause resumes from the exact remainder (not a full restart); pointer swipe-to-dismiss is on by default with corner-derived direction, distance/velocity thresholds, and spring-back; every toast plays enter/exit animations, and an opt-in stacked mode gives you the sonner-style collapsed overlay that expands on hover/focus.
  • promise() for async operations. One call shows a loading spinner and flips to success/error at settle, with a never-resurrect guard if the toast was dismissed while pending, something not every incumbent's toast.promise() documents.
  • Accessible live region out of the box. role="region" landmark, per-toast role="status" with aria-live chosen by severity (assertive for errors / warnings, polite otherwise), a real <button aria-label="Dismiss">, a decorative (aria-hidden) loading spinner, and prefers-reduced-motion support that keeps the dismissal lifecycle intact while collapsing transforms to fades.
  • Zero-config styling that re-skins to any design system. Every rendered value is a --rozie-toast-* CSS custom property with a built-in fallback, plus ready-made token bridges for shadcn/ui, Material 3, and Bootstrap 5, where the incumbents ship opinionated, harder-to-fully-reskin CSS.

What Rozie defers

sonner and react-hot-toast are extremely polished, battle-tested single-framework libraries, and on React alone they are more capable today. Promise/loading toasts, swipe-to-dismiss, the animated stack, and remaining-time-aware pause have all since shipped and are no longer gaps; the two structural stances below are deliberate, permanent choices:

  • The global toast() ergonomic. sonner / react-hot-toast let you import { toast } from '…' and call it from anywhere with zero wiring. Rozie's ref-driven model is deliberately less ergonomic: you must thread the ref to call sites (or wrap it in your own app context / store). More explicit, slightly more setup; see The global-singleton question above for why this is a permanent stance, not a gap.
  • Per-toast action-button API. Some incumbents ship a dedicated action: { label, onClick } option. @rozie-ui/toast covers this with the #toast scoped slot instead (full custom chrome, not a fixed one-button shape) rather than adding a second, narrower API surface for the same job.
  • @rozie-ui/toast is a MINOR (not yet 1.0). The surface is stable and gate-verified, but it is younger and less battle-tested than the established per-framework libraries. The full prop, event, handle, and slot tables live in the showcase + API reference.

Try it

The @rozie-ui/toast showcase + API reference documents the @rozie-ui/toast-* packages — one pre-compiled, per-framework install (npm i @rozie-ui/toast-react, etc.). There is no engine to import and no required CSS: the queue / timer behaviour and a fully-tokenised skin ship inside the component, with optional one-line theme bridges for shadcn/ui, Material 3, and Bootstrap 5. The live demo runs the real Vue package in the page.

Cross-references

Pre-1.0 — APIs may change between minor versions.