Popser

Toaster

One component. Drop it once. Forget about it.

<Toaster> renders the viewport. Drop it once, anywhere in the tree. It portals to document.body so your carefully crafted layout remains unbothered.

import { Toaster } from "@vcui/popser";

function App() {
  return (
    <>
      <YourApp />
      <Toaster />
    </>
  );
}

No Provider. No context. Toaster handles all that internally because you've got better things to do.

Props

Layout

PropTypeDefaultDescription
positionPopserPosition"bottom-right"Where toasts show up
limitnumber3Max visible toasts (collapsed)
expandedLimitnumberFalls back to limitMax visible toasts when expanded (hovered)
offsetnumber | string16Distance from viewport edge
mobileOffsetnumber | stringFalls back to offsetDistance from edge on mobile
gapnumber8Space between expanded toasts
mobileBreakpointnumber600Width that triggers mobile layout
expandbooleanfalseAlways show toasts expanded

Behaviour

PropTypeDefaultDescription
timeoutnumber4000Global auto-dismiss in ms
closeButton"always" | "hover" | "never""hover"Close button visibility
closeButtonPosition"header" | "corner""header"Where to render close buttons
swipeDirectionSwipeDirection | SwipeDirection[]["down", "right"]Swipe-to-dismiss direction(s)

Appearance

PropTypeDefaultDescription
theme"light" | "dark" | "system""system"Colour scheme
richColorsbooleanfalseColoured backgrounds per type
unstyledbooleanfalseStrip all default styles
iconsPopserIconsBuilt-in SVGsOverride icons per type
classNamesPopserClassNames—Class names for every slot
styleCSSProperties—Inline styles on the viewport

Advanced

PropTypeDefaultDescription
toastOptionsPartial<PopserOptions>—Defaults applied to every toast
ariaLabelstring—Custom ARIA label for the viewport
dir"ltr" | "rtl" | "auto"—Text direction. "auto" reads from document.documentElement.dir
historyLengthnumber—Max entries in the toast history ring buffer. 0 or unset disables

Positions

Six spots. Pick your favourite corner. Or centre, if you enjoy blocking content. Default is highlighted.

top-left
top-center
top-right
bottom-left
bottom-center
bottom-right
<Toaster position="top-center" />

Theme

"system" reads prefers-color-scheme. Or just tell it what you want:

<Toaster theme="dark" />

Sets data-theme on the viewport. Using next-themes or anything that slaps .dark on an ancestor? Popser picks it up via CSS. No config. It just works.

Mobile

Below mobileBreakpoint (default 600px), toasts switch to:

  • Full viewport width
  • Close button always visible (thumbs are imprecise)
  • mobileOffset for spacing
  • data-mobile attribute on the viewport

No hardcoded media queries. Just a prop.

<Toaster mobileBreakpoint={768} mobileOffset="24px" />

Stack

Toasts collapse into a stack by default. Hover to expand. Hate the stack? Spread everything out.

<Toaster expand />

Stack state lives on data-expanded. Mouse leave has a 100ms debounce because without it, every accidental pixel causes a seizure.

Expanded limit

Show more toasts when hovering than when collapsed. limit controls the collapsed count, expandedLimit controls the expanded count.

<Toaster limit={3} expandedLimit={8} />

Collapsed: 3 toasts visible. Hover: up to 8. The provider keeps enough toasts alive internally to support the higher count.

Rich colours

Coloured backgrounds per type. One prop.

<Toaster richColors />

Close button

Visibility

Three modes. Choose based on how much you trust your users.

<Toaster closeButton="always" />   {/* trust no one */}
<Toaster closeButton="hover" />    {/* default — civilised */}
<Toaster closeButton="never" />    {/* no escape */}

Position

Where the close button lives. "header" puts it inline in the toast header. "corner" positions it absolute in the top-right corner.

<Toaster closeButtonPosition="corner" />

Per-toast override:

toast("Important", { closeButtonPosition: "corner" });

Resolution: per-toast > Toaster > default "header".

RTL support

Right-to-left layout. Set dir to "rtl" or "auto" to read from document.documentElement.dir.

<Toaster dir="rtl" />

When RTL is active:

  • Positions flip: "top-right" → "top-left", "bottom-right" → "bottom-left" (center positions unchanged)
  • Swipe directions flip: left ↔ right
  • Entry/exit animations mirror horizontally
  • data-dir="rtl" set on the viewport for CSS targeting
<Toaster dir="auto" />  {/* reads document.documentElement.dir */}

Toast history

Opt-in ring buffer of recent toast events. Enable with historyLength.

<Toaster historyLength={50} />

Then query from anywhere:

const history = toast.getHistory();
// [{ id, title, type, createdAt, closedAt, closedBy }, ...]

toast.clearHistory();

Disabled by default (0 or unset). The buffer evicts oldest entries when over capacity. Each entry tracks closedBy: "auto", "manual", or "limit".

Custom icons

Don't like the built-in SVGs? Swap any or all. Emoji, custom components, false to kill them entirely.

<Toaster
  icons={{
    success: <MyCheckIcon />,
    error: <MyXIcon />,
    info: <MyInfoIcon />,
    warning: <MyAlertIcon />,
    loading: <MySpinner />,
    close: <MyCloseIcon />,
  }}
/>

Built-in icons use currentColor so they inherit your text colour. Per-toast override via icon prop.

Default toast options

Apply options to every toast globally. Per-toast options still override, because specificity matters.

<Toaster
  toastOptions={{
    timeout: 8000,
    richColors: true,
    classNames: {
      root: "my-toast",
      title: "my-toast-title",
    },
  }}
/>

ClassNames

Target every slot:

<Toaster
  classNames={{
    viewport: "my-viewport",
    root: "my-toast",
    content: "my-content",
    header: "my-header",
    title: "my-title",
    description: "my-description",
    icon: "my-icon",
    actions: "my-actions",
    actionButton: "my-action",
    cancelButton: "my-cancel",
    closeButton: "my-close",
    arrow: "my-arrow",
  }}
/>

Merges across three levels. Nothing replaced, everything concatenated:

  1. Toaster classNames
  2. toastOptions.classNames
  3. Per-toast classNames

Swipe direction

Control which way dismisses. Default is right and down.

<Toaster swipeDirection="right" />
<Toaster swipeDirection={["down", "left"]} />

Options: "up", "down", "left", "right". Single value or array.

Error boundary

Built in. One toast throws? The rest keep going. Errors hit the console. Your app doesn't crash because someone passed a malformed icon. You're welcome.

On this page