comps

Toast

Renderer for the headless toast queue in @plyxui/hooks. Mount the Toaster once, call toast() anywhere.


The toast queue lives in @plyxui/hooksToastProvider + useToast(). @plyxui/comps ships Toaster, the renderer: a stacked corner pile, newest on top, up to three older toasts peeking behind it. Hovering expands the pile into a column and pauses every auto-dismiss timer (a hidden tab pauses them too); enter/exit run through the Presence primitive on the motion tokens.

That split is on purpose: the same queue can drive a custom snackbar, a banner stack, or this Toaster. Apps that need a bespoke visual skip the renderer and read from useToast() directly.

Try it live

import { ToastProvider, useToast } from "@plyxui/hooks";
import { Toaster } from "@plyxui/comps";
import { Box, Button, Stack } from "@plyxui/primitives";

function Demo() {
const { toast } = useToast();
return (
  <Stack direction="vertical" gap={3} style={{ padding: 32 }}>
    <Button onClick={() => toast({ title: "Saved", variant: "success" })}>
      Fire a success toast
    </Button>
    <Button variant="ghost" onClick={() => toast({ title: "Heads up", description: "Something to look at", variant: "default" })}>
      With description
    </Button>
    <Button variant="danger" onClick={() => toast({ title: "Couldn't save", variant: "error" })}>
      Error
    </Button>
  </Stack>
);
}

export default function App() {
return (
  <ToastProvider>
    <Box style={{ minHeight: "100vh" }}>
      <Demo />
    </Box>
    <Toaster position="bottom-right" />
  </ToastProvider>
);
}

API

import { ToastProvider, useToast } from "@plyxui/hooks";
import { Toaster } from "@plyxui/comps";

function App() {
  return (
    <ToastProvider>
      <Routes />
      <Toaster position="bottom-right" />
    </ToastProvider>
  );
}

function SaveButton() {
  const { toast } = useToast();
  return (
    <Button
      onClick={async () => {
        await save();
        toast({ title: "Saved", description: "Changes live now.", variant: "success" });
      }}
    >
      Save
    </Button>
  );
}

Actions and undo

A toast can carry an action — it renders as a button, and the toast sticks around until dismissed (an Undo the user can't reach isn't an Undo). Explicit duration still wins if you pass one.

toast({
  title: "Message archived",
  action: { label: "Undo", onClick: restore },
});

Variants

default, success, warning, error. The left rail accent picks the matching theme token. Override per variant with the accent prop.

Props

PropTypeDefault
position"top-left" | "top-right" | "top-center" | "bottom-left" | "bottom-right" | "bottom-center""bottom-right"
offsetnumber (px from the corner)16
maxnumber (cap of rendered toasts; older ones animate out)5
accentPartial<Record<ToastVariant, string>>
widthnumber (stack width in px)356

The queue in @plyxui/hooks also grew pausable timers — pause(id) / resume(id) / pauseAll() / resumeAll() — which the Toaster drives on hover and tab visibility. Existing toast({ title, description, duration }) calls work unchanged.

Native

The native Toaster lives at the same import path and supports top or bottom positions. Same API, minus the stacking physics — hover doesn't exist on touch, so it renders a single column with Presence enter/exit and the same action button.