Popser

Changelog

What changed, when, and why you should care.

1.2.0

The "Sonner issues tab as a roadmap" release. Eight features, every one of them pulled from a real GitHub issue where someone asked Sonner for it and got silence. 39 new tests.

Added

  • Animation duration customization. --popser-transition-duration and --popser-anchored-transition-duration CSS custom properties. Pure CSS, zero JS. (Sonner #690)
  • Popover API support. Toast viewport uses popover="manual" + showPopover(). Toasts render in the top layer, above dialogs and modals. Progressive enhancement. (Sonner #655, #667)
  • Expanded limit. expandedLimit prop on <Toaster>. Collapsed shows limit, hover expands to expandedLimit. (Sonner #637)
  • Close button position. closeButtonPosition: "header" (default) or "corner" (absolute top-right). Per-toast override supported. (Sonner #747)
  • AbortSignal for promise toasts. signal, aborted, and onAbort on toast.promise(). Cancel in-flight requests. Default aborted type: "warning". (Sonner #681)
  • Entry direction. enterFrom: "top" | "bottom" | "left" | "right". Per-toast animation direction override. (Sonner #671)
  • RTL support. dir prop on <Toaster>: "ltr" | "rtl" | "auto". Flips positions, swipe directions, and animations.
  • Toast history. toast.getHistory() and toast.clearHistory(). Opt-in ring buffer via historyLength prop. ToastHistoryEntry type exported.

Changed

  • 7 CSS files updated for new features.
  • types.ts expanded with new option interfaces.
  • index.ts barrel export updated with ToastHistoryEntry.

1.1.2

The shadcn registry actually works now. Also fixed some docs that were lying about import names.

Added

  • shadcn registry served from the docs site. Direct install: npx shadcn add https://popser.vcui.dev/r/popser.json.
  • Namespace install: configure @vcode-sh in your components.json, then npx shadcn add @vcode-sh/popser.
  • /llms.txt — concise index of all docs pages with links and descriptions.
  • /llms-full.txt — full documentation concatenated into one plain text file. JSX demo components stripped out.

Fixed

  • shadcn docs referenced <Popser> but the registry component exports <Toaster>. Fixed everywhere.
  • README CSS examples used HSL values. The token system is OKLCH. Now they match.
  • Factual accuracy sweep across all docs: corrected @import file count (6 not 7), Base UI release date (late 2025 not early 2026), Sonner classNames count, update() API comparison, CSS bundling description, migration example, and removed unverifiable quotes.

Changed

  • Registry homepage updated from GitHub to popser.vcui.dev.

No code changes

Zero changes to src/. Same build output as 1.1.0.


1.1.1

The paperwork patch. No code changed. The docs got a promotion.

Added

  • Documentation site: popser.vcui.dev — full docs, interactive demos, changelog.
  • Why I built this — the honest version of the README's "Why" section.
  • Why Base UI, not Radix — the technical argument for headless primitives in 2026.
  • Collaborate — contribution guide with actual context, not just a checklist.
  • Interactive toast demos across docs pages (types, anchored, custom, styling).
  • Open Graph images and sitemap for the documentation site.

Changed

  • README.md rewritten with bundle size table, docs site links, and contributing section.
  • CONTRIBUTING.md rewritten with architecture reference and human tone.
  • package.json homepage now points to popser.vcui.dev.
  • Comparison docs (popser-sonner.md, popser-toast.md) updated with v1.1.0 bundle sizes.

No code changes

Zero changes to src/. Same build output as 1.1.0. If you're already on 1.1.0, this is just metadata.


1.1.0

The diet patch. Same features, half the calories.

The numbers

JS bundle:

RawGzipped
1.0.029.0 KB6.6 KB
1.1.013.4 KB4.8 KB
-54%-27%

CSS bundle (via @vcui/popser/styles/min):

RawGzipped
1.0.024.9 KB5.4 KB
1.1.014.3 KB2.6 KB
-43%-52%

Total: 7.4 KB gzipped. Sonner ships ~17 KB. We're less than half.

What actually happened

JS minification — the dist was shipping formatted, readable JavaScript. Lovely for education. Terrible for your users' bandwidth. Added minify: true to tsup. Problem solved.

Flat CSS bundle — @vcui/popser/styles uses @import directives pointing to 6 sibling files. Fine if your bundler resolves CSS imports. Not fine if you're using a <link> tag like it's 2014. New @vcui/popser/styles/min export inlines everything into one minified file.

// Still works, still modular
import "@vcui/popser/styles";

// New: single file, minified, no @imports
import "@vcui/popser/styles/min";

Promise handler dedup — toast.promise() had near-identical successHandler and errorHandler blocks. ~100 lines of copy-paste. Extracted into buildPromiseHandler(). Same behaviour, fewer lines, easier to maintain.

Dead code cleanup — three constants (DEFAULT_EXPAND, DEFAULT_RICH_COLORS, DEFAULT_THEME) were exported but never imported anywhere. tsup tree-shook them from the dist anyway, but dead code in source is still dead code. Removed.

Spinner hoist — the loading spinner creates 12 <div> elements. Previously allocated a new array on every render. Now hoisted to module scope. The React reconciler thanks us.

No breaking changes

Everything from 1.0.0 works exactly the same. The @vcui/popser/styles import is unchanged. The new @vcui/popser/styles/min is purely additive.


1.0.0

The "we're not prefixing everything with 0. anymore" release. Same library, bigger number, calmer energy.

Added

  • Close buttons now visible on all toasts when stack is expanded (hover mode). Previously only the front toast showed its close button on hover. Now when you expand the stack, every toast gets one. Revolutionary, I know.
  • Full documentation rewrite. Modular docs for every feature: API, Toaster, styling, promise toasts, anchored toasts, custom toasts, shadcn, testing. Plus updated comparison docs against Sonner and Base UI Toast. The README is no longer a wall of text pretending to be documentation.

Changed

  • Version number. That's the breaking change. The API is identical to 0.4.0. If your code worked yesterday, it works today.

0.4.0

The "everything should have been smooth from the start" release. Theme detection actually works now, animations don't jank, and the expand/collapse transition stops looking like a PowerPoint slide from 2003.

Added

  • theme="system" now resolves to actual "light" or "dark" via matchMedia("prefers-color-scheme"). Previously it just passed "system" as a string to data-theme and hoped your CSS would figure it out. Now it does the work.
  • Anchored toast enter/exit: dedicated scale(0.95) fade animation instead of inheriting the viewport slide.
  • Expanded close buttons: hover-mode close buttons show at opacity: 0.5 when the stack is expanded. Mobile close button opacity bumped from 0.5 to 0.7.

Changed

  • Expanded stack layout completely rewritten. Toasts stay position: absolute in both collapsed and expanded states. Expand/collapse is now a smooth animated transition using --toast-offset-y offsets instead of the old flex-column layout swap that caused a jarring jump. This is the big one.
  • Mobile forces bottom-center position regardless of the position prop. Because top-right toasts on a 375px screen is a crime.
  • Mouse handlers moved from individual <Toast.Root> elements to the viewport. Fewer event listeners, same behavior.
  • Exit animations: toasts now slide back in the direction they entered (bottom toasts slide down to exit, top toasts slide up). Previously they slid in the opposite direction, which looked wrong.
  • All stacking transitions use cubic-bezier(0.32, 0.72, 0, 1) easing. The old linear 400ms felt like moving furniture.
  • Arrow CSS rewritten: proper per-side positioning with border triangles. Each data-side gets its own transform, border pair, and edge pinning. The old "rotate 45deg and hope" approach is gone.
  • Spinner: contain: content replaces will-change: opacity. Less GPU layer promotion, same visual.
  • Action button hover: filter: brightness(1.15) replaces opacity: 0.9. Looks better on colored backgrounds.
  • will-change: transform, opacity added to stacking transitions for GPU acceleration.
  • Expanded viewport uses ::before pseudo-element to extend hover area instead of changing layout. Prevents Floating UI coordinate breakage for anchored toasts.
  • Gap-filling ::after pseudo-elements on expanded toasts prevent mouseLeave when moving between stacked items.

Removed

  • onMouseEnter/onMouseLeave props from PopserToastRoot. Mouse tracking is viewport-level now.

0.3.1

Changed

  • Package renamed from popser to @vcui/popser. Scoped under the @vcui org because npm naming is a land grab and I arrived late.
  • All imports, docs, and registry references updated to @vcui/popser.

0.3.0

Anchored toasts. Pin a toast to a button, a click event, or arbitrary coordinates. Think tooltips but for notifications.

Added

  • Anchored toasts via anchor prop. Accepts Element, MouseEvent, or {x, y} coordinates.
  • anchorSide, anchorAlign, anchorOffset, anchorAlignOffset, anchorPositionMethod, anchorSticky, anchorCollisionBoundary, anchorCollisionPadding — the full Floating UI integration.
  • arrow: show arrow pointing at anchor (default: false). With arrowPadding for edge spacing.
  • anchor-resolver.ts: resolves MouseEvent and {x, y} to synthetic DOM elements with automatic cleanup.
  • Only one anchored toast visible at a time. New one closes the previous.
  • Anchored toasts excluded from viewport stacking (no collapse/expand).
  • PopserAnchor type exported. data-anchored attribute on anchored toast roots.

Changed

  • Sourcemaps disabled in build output. Your bundle debugger was never going to trace through Base UI anyway.

0.2.0

The real first release. 0.1.x was a series of increasingly frantic patches that should have been one version. This is the one where it actually works properly.

Added

  • toast.custom((id) => jsx): render arbitrary JSX, bypass default layout. Renders with data-type="custom".
  • toast.message(): explicit default-type toast. toast.dismiss(): alias for toast.close(). Sonner compatibility.
  • toast.getToasts(): returns array of active toast IDs.
  • duration option: alias for timeout. Because Sonner users shouldn't have to read a migration guide for a timer prop.
  • onAutoClose(id) callback: fires when toast auto-dismisses by timeout.
  • onDismiss(id) callback: fires when user manually dismisses.
  • deduplicate option: prevents duplicate toasts with the same string title.
  • Per-toast dismissible prop. ariaLabel prop on <Toaster>.
  • Promise handlers accept ReactNode (JSX), not just strings.
  • Promise extended results: return { title, description, timeout, icon, action } from success/error handlers.
  • Promise conditional skip: return undefined to dismiss silently.
  • Promise finally handler. Lazy evaluation: pass () => promise.
  • Per-toast classNames with 3-level merge (Toaster > toastOptions > per-toast).
  • Action and cancel accept both PopserAction objects and raw ReactNode.
  • All callbacks receive toast ID as argument.
  • Internal toast data stored under data.__popser namespace.
  • data-popser-id attribute on every toast root for E2E test targeting.
  • CSS: prefers-reduced-motion disables all animations. Swipe-direction-specific exit animations.

Changed

  • Stacking rewritten: absolute positioning with CSS variables instead of flex layout.
  • Close button hover mode shows on toast hover, always visible on mobile.
  • Promise toast: queueMicrotask(() => getManager().close(id)) when handler returns undefined.

Fixed

  • Content behind non-front toasts properly hidden with opacity: 0 transition.
  • Reduced motion: all transitions disabled, not just animations.

0.1.0

First release. Everything is new. Nothing is battle-tested. Ship it.

Added

  • Imperative toast API: toast(), .success(), .error(), .info(), .warning(), .loading()
  • Promise toasts: toast.promise() with loading/success/error states
  • Toast management: toast.close(), toast.update()
  • <Toaster> component with configurable position, limit, timeout, theme
  • Built-in SVG icons for all toast types + CSS spinner for loading
  • Action and cancel buttons per toast
  • Close button with always/hover/never visibility modes
  • Rich colors support via richColors prop
  • CSS custom properties for full theming (--popser-* prefix). Light and dark mode tokens (OKLCH).
  • Mobile responsive behavior with configurable mobileBreakpoint
  • Swipe to dismiss with configurable direction(s)
  • useToaster() hook for React context consumers
  • shadcn registry support (npx shadcn add @vcode-sh/popser)
  • Default styles via @vcui/popser/styles. Separate tokens via @vcui/popser/tokens. Unstyled mode for full CSS control.
  • Stacking with expand-on-hover
  • ARIA live regions with configurable priority. F6 keyboard navigation.
  • data-popser-* attributes on every element for stable CSS/test selectors
  • ESM + CJS dual output with TypeScript declarations
  • Singleton toast manager with proper cleanup. Error boundary in Toaster.

On this page