dynaura

Recipes

Copy-ready patterns for the things toasts are actually used for. Each one has a live demo.

Success, error and actions

The island expands to show the description and button, then collapses before it dismisses. A button click dismisses the toast.

tsx
dynaura.error({
  title: "Payment failed",
  description: "Card ending 4242 was declined.",
  button: { title: "Retry", onClick: retry },
});

Promises

The same island morphs from loading to success or error. success and error can be strings, option objects, or functions of the result. The original promise is returned.

tsx
dynaura.promise(save(), {
  loading: "Saving...",
  success: (data) => ({ title: "Saved", description: `Version ${data.v}` }),
  error: { title: "Failed", description: "Check your connection." },
});

Progress

Create a sticky loading toast, then patch it with dynaura.update. Frequent progress updates do not restart the toast. Pass progress: undefined to clear the bar.

tsx
const id = dynaura.loading({ title: "Uploading", progress: 0 });

onProgress((p) => dynaura.update(id, { progress: p })); // 0 to 1

dynaura.update(id, {
  type: "success",
  title: "Upload complete",
  progress: undefined, // clear the bar
  duration: 4000,
  confetti: true,
});

Live activities

Sticky toasts with a running timer by default, or an animated waveform with kind: "wave".

tsx
dynaura.live({
  title: "Recording voice memo",
  button: { title: "Stop", onClick: stop },
});

dynaura.live({ title: "Now playing", kind: "wave" }); // waveform instead of a timer

Rich cards

variant: "card" makes a header tab plus any custom body, joined by a curved notch. Card content inherits colors through var(--dynaura-fg), var(--dynaura-muted) and var(--state), so it follows any theme. The header tab follows position: centered for top and bottom, flush to the edge for -left and -right.

tsx
dynaura.info({
  title: "Boarding soon",
  trailing: "40 min",
  variant: "card",
  duration: 9000,
  description: <BoardingPass />, // any React node (a DOM Node in Vue, Svelte and vanilla)
  button: { title: "Show boarding pass", onClick: openPass },
});

Queue

Show toasts one after another instead of replacing the current one. Set it on the Toaster, or per toast.

tsx
// every toast waits its turn
<Toaster queue />

// or per toast
dynaura.info({ title: "Invite sent to Maya", queue: true, duration: 1600 });

Custom icons and slots

icon replaces the built-in icon. trailing is a small right-side slot.

tsx
import { CalendarDaysIcon } from "@heroicons/react/24/outline";

dynaura.info({
  title: "Design sync starts soon",
  icon: <CalendarDaysIcon width={16} height={16} />, // replaces the built-in icon
  trailing: "5 min", // small right-side slot
});

Sticky, update and dismiss

duration: null keeps a toast open. Every call returns an id, so you can patch it with update (including its type) or remove it with dismiss(id). dismiss() with no id closes the current toast, and clear() removes everything including the queue.

tsx
const id = dynaura.info({ title: "Sticky until dismissed", duration: null });
dynaura.dismiss(id);      // one toast
dynaura.dismiss();        // the current toast, then the next queued one shows
dynaura.clear();          // everything

const sync = dynaura.loading({ title: "Syncing" });
dynaura.update(sync, { type: "success", title: "Synced", duration: 3500 });

Positions

Six positions: top, top-left, top-right, bottom, bottom-left and bottom-right. Safe-area insets are added to offset. This changes the island on this page, so reload to reset it.

tsx
<Toaster position="bottom-right" offset={16} />