Popser

Custom Toasts

Render whatever you want. We give you the wrapper. You bring the chaos.

toast.custom() lets you render arbitrary JSX inside a toast. No default layout. No icon. No title slot. Just a wrapper with animations and whatever you shove inside it.

Usage

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

toast.custom((id) => (
  <div style={{ display: "flex", gap: 12, alignItems: "center" }}>
    <img src="/avatar.png" width={40} height={40} />
    <div>
      <strong>New message</strong>
      <p>Hey, are you coming to the meeting?</p>
    </div>
    <button onClick={() => toast.close(id)}>Dismiss</button>
  </div>
));

You get the toast id. Use it to close things. Or don't. Let it time out. Your funeral.

Examples

Different shapes for different moods. All custom, all yours.

Anchored custom

Custom toasts work with anchoring too. Pin your own JSX to an element.

toast.custom(
  (id) => <MyTooltip id={id} />,
  {
    anchor: buttonRef.current,
    anchorSide: "top",
    arrow: true,
    timeout: 2000,
  }
);

What you get

Even in full custom mode, you're not completely abandoned:

  • Toast root wrapper (data-popser-root)
  • Enter/exit animations
  • Swipe-to-dismiss (unless dismissible: false)
  • Proper stacking in the viewport
  • data-popser-id for testing
  • Anchor positioning (if anchor is set)
  • Arrow (if arrow: true and anchor is set)

What you don't get

The entire default layout is skipped:

  • No icon
  • No title/description slots
  • No action/cancel buttons
  • No close button
  • No content wrapper

You want any of that? Build it. That's the deal.

Options

Same options as other methods, minus the content ones. Everything else works.

toast.custom(
  (id) => <MyComponent id={id} />,
  {
    id: "custom-toast",
    timeout: 5000,
    dismissible: true,
    deduplicate: true,
    anchor: element,
    anchorSide: "top",
    arrow: true,
    onClose: (id) => console.log("closed", id),
    onRemove: () => console.log("gone"),
    data: { source: "chat" },
  }
);

Styling

Custom toasts are transparent by design. No background, no border, no shadow, no padding. Your JSX renders clean without fighting default chrome.

Want the standard toast look back? Pass style with popser's CSS variables:

toast.custom(
  (id) => <MyComponent id={id} />,
  {
    style: {
      background: "var(--popser-bg)",
      border: "1px solid var(--popser-border)",
      borderRadius: "var(--popser-radius, 8px)",
      boxShadow: "var(--popser-shadow)",
      padding: 12,
      color: "var(--popser-fg)",
    },
  }
);

Works in light and dark mode. The tokens resolve automatically.

Or target custom toasts globally in CSS — they get data-type="custom" on the root:

[data-popser-root][data-type="custom"] {
  background: var(--popser-bg);
  border: 1px solid var(--popser-border);
  border-radius: var(--popser-radius, 8px);
  box-shadow: var(--popser-shadow);
  padding: 12px;
  color: var(--popser-fg);
}

All standard data attributes still apply (data-popser-id, data-rich-colors, data-anchored, etc.).

On this page