Documentation

Everything you need to get started with toast-23 v2.0.0.

Getting Started

Install toast-23 and create your first toast in minutes.

Installation

Install toast-23 using your preferred package manager:

npm
bash
npm install toast-23
yarn
bash
yarn add toast-23
pnpm
bash
pnpm add toast-23
bun
bash
bun add toast-23

Quick Start

v2 update: styles are auto-injected the first time Toast23Provider or createToast23() mounts. No import "toast-23/styles.css" step required. The CSS subpath export still ships if you want to load it yourself for SSR-FOUC prevention or to override the cascade order.

1Wrap your app with the provider

The Toast23Provider manages toast state and renders toast containers.

App.tsx
tsx
import { Toast23Provider } from "toast-23";

export default function App() {
  return (
    <Toast23Provider
      position="top-right"
      maxVisible={5}
      duration={5000}
    >
      <YourApp />
    </Toast23Provider>
  );
}

2Use the hook in any component

Call useToast() to get the toast API with variant shortcuts.

MyComponent.tsx
tsx
import { useToast } from "toast-23";

function MyComponent() {
  const toast = useToast();

  return (
    <div>
      <button onClick={() => toast("Hello world!")}>
        Default
      </button>
      <button onClick={() => toast.success("Saved!")}>
        Success
      </button>
      <button onClick={() => toast.error("Oops!")}>
        Error
      </button>
    </div>
  );
}

Usage Guide

Learn how to use every feature of toast-23.

Variants

toast-23 includes 5 visual variants, each with distinct colors for both light and dark mode:

success

toast.success()

Completed actions, saved data

error

toast.error()

Failed operations, validation errors

warning

toast.warning()

Potential issues, important notices

info

toast.info()

General information, updates

default

toast()

Neutral messages

Loading Toast

Create a loading notification. Most likely, you'll want to update it afterwards. For a friendly alternative, check out toast.promise(), which takes care of that automatically.

LoadingToast.tsx
tsx
const toast = useToast();

// Show loading toast
const toastId = toast.loading("Waiting...");

// Later, update it to success
toast.success("Done!", { id: toastId });

Tip: Loading toasts are persistent by default (duration: 0) and non-dismissible. Update them with a new variant when your operation finishes.

Promise API

Track async operations with automatic state transitions. The toast updates automatically when the promise resolves or fails.

Simple Usage

PromiseSimple.tsx
tsx
const toast = useToast();

const myPromise = fetchData();

toast.promise(myPromise, {
  loading: "Loading",
  success: "Got the data",
  error: "Error when fetching",
});

Advanced

Provide a function to the success/error messages to incorporate the result/error. The third argument accepts toast options:

PromiseAdvanced.tsx
tsx
toast.promise(
  myPromise,
  {
    loading: "Loading",
    success: (data) => `Successfully saved ${data.name}`,
    error: (err) => `This just happened: ${err.toString()}`,
  },
  {
    duration: 5000,
    position: "bottom-center",
  }
);

Using an Async Function

You can also provide a function that returns a promise, which will be called automatically:

PromiseAsync.tsx
tsx
toast.promise(
  async () => {
    const { id } = await fetchData1();
    await fetchData2(id);
  },
  {
    loading: "Loading",
    success: "Got the data",
    error: "Error when fetching",
  }
);

Tip: It's recommended to set a minWidth on your promise toasts to prevent layout jumps from different message lengths.

Custom (JSX)

Create a fully custom notification with JSX. Custom toasts render your content without any default styles.

CustomJSX.tsx
tsx
toast.custom(<div>Hello World</div>);

Render JSX with Default Styles

To render custom JSX content with the default toast styling, pass JSX directly to toast() instead:

JSXWithStyles.tsx
tsx
toast(
  <span>
    Custom and <b>bold</b>
  </span>,
  {
    title: "My Custom Toast",
  }
);

Dismiss from Custom Content

Use the returned toast id to add dismiss buttons inside your custom content:

CustomDismiss.tsx
tsx
const toastId = toast.custom(
  <div className="my-custom-toast">
    <p>Something happened</p>
    <button onClick={() => toast.dismiss(toastId)}>
      Dismiss
    </button>
  </div>
);

Positioning

Set a default position on the provider, and optionally override per-toast:

Positioning.tsx
tsx
// Default position for all toasts
<Toast23Provider position="bottom-right">
  <App />
</Toast23Provider>

// Override position for a specific toast
toast.success("Saved!", { position: "top-left" });

Click a position to preview a real toast notification:

Toast Options

Every toast method accepts an optional options object:

Options.tsx
tsx
toast.success("Saved!", {
  title: "Auto-save",
  duration: 6000,
  position: "bottom-right",
  dismissible: true,
});

// Persistent toast (must be dismissed manually)
const id = toast.info("Processing...", { duration: 0 });

// Later, dismiss it
toast.dismiss(id);

Default Durations

Every type has its own default duration. You can overwrite them with toast options per-toast or globally via the provider.

TypeDuration
success5000ms (provider default)
error5000ms (provider default)
warning5000ms (provider default)
info5000ms (provider default)
default5000ms (provider default)
loadingInfinity (persistent)
custom5000ms (provider default)
Durations.tsx
tsx
// Override provider default duration
<Toast23Provider duration={3000}>
  <App />
</Toast23Provider>

// Override per-toast
toast.success("Quick!", { duration: 2000 });

Dismiss & Remove

Programmatically control toast lifecycle with dismiss (animated exit) and remove (instant removal).

Dismiss a Single Toast

Triggers the exit animation, then removes after removeDelay (default 1000ms):

DismissSingle.tsx
tsx
const toastId = toast.loading("Loading...");

// Later...
toast.dismiss(toastId);

Dismiss All Toasts

Omit the id to dismiss all visible toasts at once:

DismissAll.tsx
tsx
toast.dismiss();

Remove Toasts Instantly

To remove toasts instantly without any exit animation, use toast.remove():

RemoveInstant.tsx
tsx
// Remove a specific toast
toast.remove(toastId);

// Remove all toasts
toast.remove();

Configure Remove Delay

By default, toasts are kept in the DOM for 1000ms after being dismissed (to play exit animation). Configure per-toast or globally:

RemoveDelay.tsx
tsx
// Per-toast
toast.success("Created!", { removeDelay: 500 });

// Globally via provider
<Toast23Provider
  duration={5000}
  // All toast options can be set as defaults
>
  <App />
</Toast23Provider>

Update & Deduplicate

Update an Existing Toast

Each toast call returns a unique id. Pass it as the id option to update an existing toast in-place:

UpdateExisting.tsx
tsx
const toastId = toast.loading("Loading...");

// Later, update to success
toast.success("This worked", {
  id: toastId,
});

Prevent Duplicate Toasts

To prevent duplicates, provide a unique permanent id. If a toast with that id already exists, it will be updated instead of creating a new one:

PreventDuplicates.tsx
tsx
toast.success("Copied to clipboard!", {
  id: "clipboard",
});

Action Buttonsv2

Add an inline action (Undo, Retry, Open) via the action option. Pair it with cancelAction and the toast switches to a stacked two-button confirm layout.

ActionExample.tsx
tsx
toast.success("Message archived", {
  action: {
    label: "Undo",
    onClick: () => restoreMessage(),
  },
});

Controlling dismissal

By default, clicking the action button closes the toast. Set dismissOnClick: false to keep the toast open while the handler runs, and call the dismiss helper that onClickreceives to close it explicitly when you're ready.

ActionDismissControl.tsx
tsx
toast.loading("Saving draft…", {
  action: {
    label: "Cancel save",
    dismissOnClick: false,
    onClick: (dismiss) => {
      saveController.abort();
      dismiss();
    },
  },
});

onDismiss callback

Fires exactly once per toast on any dismiss path manual close, action click, swipe, timeout, programmatic dismiss(), or group dismissal. Use it to run cleanup tied to the lifetime of a specific toast.

OnDismiss.tsx
tsx
const id = toast.loading("Uploading...", {
  onDismiss: () => abortController.abort(),
});

// later — any of these will trigger onDismiss exactly once
toast.dismiss(id);
// user clicks ×
// user swipes the toast away
// the toast auto-dismisses

Toast Groupsv2

Tag related toasts with a group string, then dismiss or remove the whole batch in one call. Useful for upload queues, long-running operations, or any cluster of related notifications.

Groups.tsx
tsx
toast.info("Uploading file 1...", { group: "upload" });
toast.info("Uploading file 2...", { group: "upload" });
toast.info("Uploading file 3...", { group: "upload" });

// Later, clear the entire upload group:
toast.dismissGroup("upload");

// Or remove instantly (no exit animation):
toast.removeGroup("upload");

Pause & Resumev2

Freeze every active toast's auto-dismiss timer with toast.pauseAll(), then resume them with toast.resumeAll(). Handy when the user opens a dialog or you want toasts to wait until the page is interactive.

ModalShell.tsx
tsx
function ModalShell({ open, children }) {
  useEffect(() => {
    if (open) toast.pauseAll();
    else toast.resumeAll();
  }, [open]);

  return open ? <Modal>{children}</Modal> : null;
}

Toast Historyv2

Every dismissed toast is recorded in an in-memory ring buffer. Read it with toast.history()to build a "recent notifications" inbox, power an undo flow, or assert in tests that the right toasts were emitted.

HistoryInbox.tsx
tsx
const recent = toast.history();
// → readonly array of { id, message, title, variant, dismissedAt, group }

// Show the last 5 in an inbox dropdown
return (
  <ul>
    {recent.slice(0, 5).map((entry) => (
      <li key={entry.id}>
        [{entry.variant}] {String(entry.message)}
      </li>
    ))}
  </ul>
);

The buffer holds the most recent 50 entries by default. Override with the historySize prop on Toast23Provider.

History Entryv2

ToastHistoryEntry

Shape of each entry returned by toast.history(). Read-only.

types.ts
typescript
interface ToastHistoryEntry {
  id: string;
  message: string | ReactNode;
  title?: string;
  variant: ToastVariant;
  dismissedAt: number;
  group?: string;
}
  • variant collapses "loading" down to "info", so consumers never see the transient loading state in history.
  • dismissedAt is a millisecond timestamp from Date.now(). Wrap it with new Date(entry.dismissedAt) for a Date object.
  • Entries are frozen snapshots of the toast at the moment of dismissal not live references. Mutating one has no effect on the queue.

Stack Layoutv2

Switch the provider into stacked mode and toasts collapse into a tidy pile that expands on hover. Same API, denser visual.

App.tsx
tsx
<Toast23Provider layout="stack" maxVisible={3}>
  <App />
</Toast23Provider>

Background toasts are scaled and offset by index. Hovering the stack reveals the full column.

RTL Supportv2

Fully opt-in via the dir prop. Defaults to "ltr" and never auto-flips, so RTL applications stay in full control.

App.tsx
tsx
<Toast23Provider dir="rtl">
  <App />
</Toast23Provider>

Inline Modev2

Pass an HTMLElement as target and toasts render inside that element instead of document.body. Useful for inspector panels, modal-scoped toasts, or any UI that needs notifications bounded to a region.

InlinePanel.tsx
tsx
function Panel() {
  const hostRef = useRef<HTMLDivElement>(null);
  const [host, setHost] = useState<HTMLDivElement | null>(null);
  useEffect(() => setHost(hostRef.current), []);

  return (
    <div ref={hostRef} className="relative h-64 overflow-hidden">
      <Toast23Provider target={host} position="top-right">
        <PanelContent />
      </Toast23Provider>
    </div>
  );
}

Headless Hookv2

useToast23Headless()subscribes to the toast queue without rendering any UI. Build a completely bespoke toast surface (Slack-style sidebar, status bar, terminal output) while keeping the lib's scheduling, timers, and history.

CustomToaster.tsx
tsx
import { useToast23Headless } from "toast-23";

function CustomToaster() {
  const { toasts, dismiss, isPausedGlobally, pauseAll, resumeAll } =
    useToast23Headless();

  return (
    <aside className="my-sidebar">
      <header>
        <strong>Notifications ({toasts.length})</strong>
        <button onClick={isPausedGlobally ? resumeAll : pauseAll}>
          {isPausedGlobally ? "Resume" : "Pause"}
        </button>
      </header>
      {toasts.map((t) => (
        <article key={t.id} data-variant={t.variant}>
          <span>[{t.variant}]</span>
          <span>{typeof t.message === "string" ? t.message : "…"}</span>
          <button onClick={() => dismiss(t.id)}>×</button>
        </article>
      ))}
    </aside>
  );
}

The hook returns an object not just the queue so the same call gives you toasts plus dismiss / remove / dismissGroup / removeGroup / pauseAll / resumeAll / history and the current isPausedGlobally flag. Subscribes via useSyncExternalStore so only headless consumers re-render on queue changes; regular useToast() users are unaffected.

Swipe to Dismissv2

Toasts can be flicked away with a pointer or touch drag. On by default with an 80 px threshold. Disable globally or tune the threshold via provider props.

App.tsx
tsx
<Toast23Provider
  swipeEnabled        // default: true
  swipeThreshold={120} // default: 80
>
  <App />
</Toast23Provider>

Notification Fallbackv2

When the user has switched to a different tab or minimized the window, an in-page toast can't reach them. Opt in to fallbackToNotification and toast-23 surfaces a real OS notification only when document.hidden is true, so the same call works in-page when visible and out-of-page when not.

Enable globally

App.tsx
tsx
<Toast23Provider fallbackToNotification>
  <App />
</Toast23Provider>

Enable per-toast

BackgroundAlerts.tsx
tsx
toast.error("Upload failed", { fallbackToNotification: true });
toast.success("Build done", { fallbackToNotification: true });

Behavior

  • Permission is requested lazily, on the first toast that would fall back. Subsequent denials don't re-prompt.
  • Notifications use tag: "toast-23", so a rapid burst replaces in place at the OS level rather than stacking.
  • Title defaults to [variant] if no title option is set. The toast message becomes the notification body.
  • Only string messages are supported; ReactNode content is ignored by the fallback.
  • When the tab is visible, the in-page toast renders normally and no notification fires.

Permission: Browsers require Notification.requestPermission() to run from a user gesture. If your first fallbackToNotification toast fires from a non-user-initiated path (e.g. a websocket event), the prompt may be blocked. Trigger one toast from a click first to warm the permission, then rely on the fallback from anywhere.

Dark Mode

toast-23 supports dark mode in three ways:

Automatic

Respects the user's system preference via prefers-color-scheme: dark. No configuration needed.

Manual Toggle

Add the dark class to any ancestor element (typically <html>). Compatible with Tailwind CSS and Next.js themes.

theme propv2

Force the toast theme regardless of OS preference or ancestor class. Useful when toasts portal to document.body and an ancestor .darkwrapper can't reach them.

App.tsx
tsx
<Toast23Provider theme={isDark ? "dark" : "light"}>
  <App />
</Toast23Provider>

Accepts "auto" (default; follows OS and ancestor .dark), "light", or "dark".

Accessibility

toast-23 is built with assistive-tech and keyboard users in mind. Most behavior is on by default; everything is configurable.

ARIA live regions

Toast containers are wrapped in an aria-live region so screen readers announce new toasts as they arrive. Error toasts use aria-live="assertive" to interrupt the current utterance; all other variants use polite so they queue behind whatever the user is already reading.

Focus shortcut v2

Pressing the F8 key from anywhere on the page jumps focus into the toast container, so keyboard users can tab through actions / dismiss buttons without hunting for the toasts visually. Disable by passing focusShortcut={null}, or rebind by passing any other KeyboardEvent.key value.

App.tsx
tsx
<Toast23Provider focusShortcut="F9">
  <App />
</Toast23Provider>

// Disable entirely
<Toast23Provider focusShortcut={null}>
  <App />
</Toast23Provider>

Reduced motion

Animations honour prefers-reduced-motion. When the OS reports reduced-motion is preferred, toast enter/exit transitions, the progress-bar shrink, and the stack-mode parallax all collapse to instant transitions so vestibular users don't experience motion sickness.

Dismiss callback

Pass onDismiss to be notified when a toast leaves, regardless of the path (user click, timer, programmatic dismiss, swipe). Fires exactly onceper toast, so it's safe to use for cleanup (cancel an in-flight request, revert an optimistic update, etc.).

OnDismiss.tsx
tsx
const controller = new AbortController();
fetch("/api/upload", { signal: controller.signal });

toast.loading("Uploading…", {
  id: "upload",
  onDismiss: () => controller.abort(),
});

Soundsv2

Toast sounds are off by default and only play when sound: true is set on the provider or per-toast. Each variant has its own short tone patch tuned to the band where humans hear best, so error and warning actually carry on laptop speakers.

Enable globally

App.tsx
tsx
<Toast23Provider sound>
  <App />
</Toast23Provider>

Enable per-toast

OneOffSound.tsx
tsx
toast.success("Saved", { sound: true });
toast.error("Something broke", { sound: true });

Note:The browser's autoplay policy requires a user gesture before audio plays. toast-23 resumes the audio context on the first toast fired after a user interaction, so no setup is required.

Confirm Toastv2

toast.confirm() returns a Promise<boolean> that resolves to true when the user confirms, or false on cancel or dismiss. The two action buttons stack on their own row below the message for a clearer decision UI.

ConfirmExample.tsx
tsx
const ok = await toast.confirm("Delete this file?", {
  confirmLabel: "Delete",
  cancelLabel: "Keep",
});

if (ok) {
  await deleteFile();
  toast.success("File deleted.");
}

Playgroundv2

The playground is an interactive sandbox where every public feature of toast-23 is wired to a button. Use it to preview variants, positions, layouts, and themes before reaching for the API.

Open playgroundAlso reachable from the Playground link in the navbar (on non-docs pages) and from the mobile docs sidebar.

What you can do

  • Fire every variant (default, success, error, warning, info, loading) from a button grid.
  • Flip the provider config live: position, layout (default vs stack), direction, max visible, dark mode, sounds.
  • Try promise toasts (with and without determinate progress), grouped dismissal, pause/resume, action and confirm patterns.
  • Inspect inline-mode toasts mounted inside a bounded container, and the headless renderer driving its own queue UI.

Toast23 DevTools

The playground ships with the optional Toast23DevTools panel pinned to the bottom-right corner. It lists every active toast, lets you inspect their variant and timing, and provides quick dismiss controls. The panel works the same way in any app you mount it in.

App.tsx
tsx
import { Toast23Provider, Toast23DevTools } from "toast-23";

<Toast23Provider>
  <App />
  {process.env.NODE_ENV !== "production" && (
    <Toast23DevTools position="bottom-right" collapsed={false} />
  )}
</Toast23Provider>

Tip: Gate Toast23DevTools behind a dev-only check so it never ships to production.

Customization

toast-23 is fully customizable. Override any CSS class to match your brand or design system.

Title is Optional

Toasts work perfectly without a title, just pass your message directly:

TitleOptional.tsx
tsx
// Without title — clean and minimal
toast.success("Changes saved successfully!");

// With title — more descriptive
toast.success("Changes saved!", {
  title: "Auto-save",
});

Custom Duration

Control how long each toast stays visible, or make it persistent:

CustomDuration.tsx
tsx
// Quick notification (3 seconds)
toast("Quick message", { duration: 3000 });

// Longer display (10 seconds)
toast.info("Please read carefully", { duration: 10000 });

// Persistent — stays until manually dismissed
const id = toast.warning("Action required", { duration: 0 });
toast.dismiss(id);

Dismiss Control

Choose whether users can manually dismiss a toast:

DismissControl.tsx
tsx
// Non-dismissible toast (no X button)
toast.info("Processing...", { dismissible: false });

// Dismissible (default behavior)
toast.success("Done!", { dismissible: true });

Override Variant Colors

Customize the look of any variant by overriding its CSS class:

custom-overrides.css
css
.toast23-item--success {
  background: #d1fae5;
  border-color: #6ee7b7;
}
.toast23-progress--success {
  background: #10b981;
}
.toast23-icon--success {
  color: #059669;
}

Custom Fonts & Sizing

Change the toast font, size, border radius, or width:

custom-fonts.css
css
.toast23-item {
  font-family: "Inter", sans-serif;
  border-radius: 1rem;
  max-width: 400px;
}
.toast23-title {
  font-size: 0.9rem;
}
.toast23-message {
  font-size: 0.85rem;
}

Progress Bar Styling

Adjust the progress bar height, color, or border radius:

custom-progress.css
css
.toast23-progress {
  height: 4px;
  border-radius: 0;
}
/* Custom color for all variants */
.toast23-progress--info {
  background: linear-gradient(90deg, #6366f1, #8b5cf6);
}

Per-Toast Position Override

Each toast can override the provider's default position:

PositionOverride.tsx
tsx
// Default position is "top-right" from provider
toast.success("Saved!", { position: "bottom-center" });
toast.error("Oops!", { position: "top-left" });

SSR Support

toast-23 is fully SSR-compatible. All browser APIs are guarded behind useEffect.

Next.js (App & Pages Router)
Remix
Gatsby
Astro (React islands)
Angular (standalone API)
Vue.js (standalone API)

Next.js

Works with both the App Router and Pages Router. Add the provider and stylesheet in your root layout or _app.tsx.

app/layout.tsx
tsx
// app/layout.tsx (App Router)
"use client";
import { Toast23Provider } from "toast-23";

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <Toast23Provider position="top-right">
          {children}
        </Toast23Provider>
      </body>
    </html>
  );
}

Remix

Import the stylesheet in your root route and wrap your Outlet with the provider.

app/root.tsx
tsx
// app/root.tsx
import { Toast23Provider } from "toast-23";
import { Outlet } from "@remix-run/react";

export default function App() {
  return (
    <html>
      <body>
        <Toast23Provider position="top-right">
          <Outlet />
        </Toast23Provider>
      </body>
    </html>
  );
}

Gatsby

Use wrapRootElement in gatsby-browser.js and gatsby-ssr.js to add the provider.

gatsby-browser.js
jsx
// gatsby-browser.js & gatsby-ssr.js
import React from "react";
import { Toast23Provider } from "toast-23";

export const wrapRootElement = ({ element }) => (
  <Toast23Provider position="top-right">
    {element}
  </Toast23Provider>
);

Astro

Use toast-23 inside React islands. Mark the wrapper component with client:load.

index.astro
astro
---
// src/pages/index.astro
---
<html>
  <body>
    <ToastWrapper client:load />
  </body>
</html>
ToastWrapper.tsx
tsx
// src/components/ToastWrapper.tsx
import { Toast23Provider, useToast } from "toast-23";

function Inner() {
  const toast = useToast();
  return <button onClick={() => toast.success("Hello!")}>Toast</button>;
}

export default function ToastWrapper() {
  return (
    <Toast23Provider position="top-right">
      <Inner />
    </Toast23Provider>
  );
}

Angular

toast-23 ships a built-in createToast23() standalone API that works outside of React. No manual React root setup required, just install the peer dependencies and use the imperative API in an Angular service.

terminal
bash
// Install peer dependencies
npm install react react-dom toast-23
npm install -D @types/react @types/react-dom
toast.service.ts
typescript
// src/app/toast.service.ts
import { Injectable, OnDestroy } from '@angular/core';
import { createToast23, StandaloneToastApi } from 'toast-23';

@Injectable({ providedIn: 'root' })
export class ToastService implements OnDestroy {
  private toast: StandaloneToastApi;

  constructor() {
    this.toast = createToast23({
      position: 'top-right',
      maxVisible: 5,
      duration: 5000,
    });
  }

  success(msg: string) { this.toast.success(msg); }
  error(msg: string)   { this.toast.error(msg); }
  warning(msg: string) { this.toast.warning(msg); }
  info(msg: string)    { this.toast.info(msg); }
  show(msg: string)    { this.toast(msg); }

  ngOnDestroy() {
    this.toast.destroy();
  }
}
example.component.ts
typescript
// Usage in any Angular component
import { Component } from '@angular/core';
import { ToastService } from './toast.service';

@Component({
  selector: 'app-example',
  template: `<button (click)="notify()">Show Toast</button>`,
})
export class ExampleComponent {
  constructor(private toast: ToastService) {}

  notify() {
    this.toast.success('Hello from Angular!');
  }
}

Vue.js

Use the built-in createToast23() standalone API as a Vue plugin. No manual React root setup needed — toast-23 handles it internally.

terminal
bash
// Install peer dependencies
npm install react react-dom toast-23
npm install -D @types/react @types/react-dom
plugins/toast.ts
typescript
// src/plugins/toast.ts
import { Plugin, inject } from 'vue';
import { createToast23, type StandaloneToastApi } from 'toast-23';

const TOAST_KEY = Symbol('toast');

export const toast23Plugin: Plugin = {
  install(app) {
    const toast = createToast23({
      position: 'top-right',
      maxVisible: 5,
      duration: 5000,
    });

    // Expose via provide/inject
    app.provide(TOAST_KEY, toast);

    // Cleanup on app unmount
    app.config.globalProperties.$toast = toast;
    const originalUnmount = app.unmount.bind(app);
    app.unmount = () => {
      toast.destroy();
      originalUnmount();
    };
  },
};

export function useToast23() {
  return inject<StandaloneToastApi>(TOAST_KEY)!;
}
main.ts
typescript
// main.ts
import { createApp } from 'vue';
import App from './App.vue';
import { toast23Plugin } from './plugins/toast';

createApp(App).use(toast23Plugin).mount('#app');
ExampleComponent.vue
vue
<!-- ExampleComponent.vue -->
<script setup lang="ts">
import { useToast23 } from '../plugins/toast';
const toast = useToast23();
</script>

<template>
  <button @click="toast.success('Hello from Vue!')">
    Show Toast
  </button>
</template>

API Reference

Complete API documentation for every export in toast-23.

Provider Setup

<Toast23Provider>

Context provider that manages toast state and renders toast containers.

Usage
tsx
import { Toast23Provider } from "toast-23";

<Toast23Provider
  position="top-right"
  maxVisible={5}
  duration={5000}
>
  <App />
</Toast23Provider>
PropTypeDefaultDescription
childrenReactNodeYour application content (required).
positionToastPosition"top-right"Default screen corner for toasts.
maxVisiblenumber5Maximum simultaneously visible toasts.
durationnumber5000Default auto-dismiss duration (ms). 0 = persistent.
layout"default" | "stack""default"Visual layout — `stack` collapses background toasts and expands on hover.
dir"ltr" | "rtl""ltr"Layout direction. Fully opt-in; never auto-flips.
theme"auto" | "light" | "dark""auto"Force a theme. `auto` follows OS + ancestor `.dark` class.
targetHTMLElement | nullnullWhere to render the portal. Pass an element for inline mode.
historySizenumber50Dismissed toasts to retain for `toast.history()`.
focusShortcutstring | null"F8"Keyboard shortcut to focus the toast container. `null` disables.
swipeEnabledbooleantrueEnable swipe-to-dismiss for pointer + touch.
swipeThresholdnumber80Pixel distance past which a swipe dismisses.
soundbooleanfalseDefault `sound` option applied to every toast.
fallbackToNotificationbooleanfalseDefault fallback to OS notifications when the tab is hidden.

Toast Hook

useToast()

React hook that returns the ToastApi. Must be called inside a <Toast23Provider>.

Usage
tsx
import { useToast } from "toast-23";

function MyComponent() {
  const toast = useToast();
  // toast is a callable function with method shortcuts
}

Note: Calling useToast() outside of a <Toast23Provider> will throw an error.

Standalone API

createToast23()

Standalone / imperative API for use outside React — works with Angular, Vue, Svelte, or vanilla JavaScript. Internally bootstraps a minimal React root.

Usage
typescript
import { createToast23 } from "toast-23";

const toast = createToast23({
  position: "top-right",
  maxVisible: 5,
  duration: 5000,
});

toast.success("Saved!");
toast.error("Oops!");
toast("Hello, world!");

// Cleanup when done
toast.destroy();
OptionTypeDefaultDescription
positionToastPosition"top-right"Default screen corner for toasts.
maxVisiblenumber5Maximum simultaneously visible toasts.
durationnumber5000Default auto-dismiss duration (ms). 0 = persistent.
layout"default" | "stack""default"Visual layout — `stack` collapses background toasts and expands on hover.
dir"ltr" | "rtl""ltr"Layout direction. Fully opt-in; never auto-flips.
historySizenumber50Dismissed toasts to retain for `.history()`.
soundbooleanfalseDefault `sound` option applied to every toast.
fallbackToNotificationbooleanfalseDefault fallback to OS notifications when the tab is hidden.
swipeEnabledbooleantrueEnable swipe-to-dismiss for pointer + touch.
swipeThresholdnumber80Pixel distance past which a swipe dismisses.

Return value: A StandaloneToastApi object exposing every method on useToast() (.success, .error, .warning, .info, .loading, .custom, .promise, .confirm, .dismiss, .remove, .dismissGroup, .removeGroup, .pauseAll, .resumeAll, .history) plus a .destroy() method that unmounts the internal React root and removes the portal.

Not available outside React: theme, target, and focusShortcutare React-specific and aren't accepted by createToast23().

DevTools

<Toast23DevTools>v2

An opt-in inspector panel for the toast queue. Drop it anywhere inside Toast23Provider (or at the app root). Gate behind a dev-only check so it never ships to production.

App.tsx
tsx
import { Toast23DevTools } from "toast-23";

{process.env.NODE_ENV !== "production" && (
  <Toast23DevTools position="bottom-right" />
)}

Props

PropTypeDefaultDescription
position"top-left" | "top-right" | "bottom-left" | "bottom-right""bottom-left"Screen corner to dock the panel.
collapsedbooleantrueStart in the small floating chip state. Click to open.

What you get

  • Tabs: Queue (live), History (dismissed), Settings.
  • Variant filter chips at the top of Queue and History toggle each variant on/off to narrow the list.
  • Replay any history entry to re-fire it with the same variant, title, and group.
  • Copy as JSON on every row (and on the provider config) handy for bug reports.
  • Settings tab:spawn a test toast in any variant, preview each variant's tone patch, override the test toast's position and duration, and read the live provider config.
  • Three sizes: collapsed chip (small floating pill), compact panel (360×480), and expanded panel (620×720). Toggle from the header.

Methods

ToastApi

The object returned by useToast(). A callable function with additional method properties.

MethodSignatureReturns
toast()(message: string | ReactNode, options?: ToastOptions)string (id)
toast.success()(message: string, options?)string (id)
toast.error()(message: string, options?)string (id)
toast.warning()(message: string, options?)string (id)
toast.info()(message: string, options?)string (id)
toast.loading()(message: string, options?)string (id)
toast.custom()(content: ReactNode, options?)string (id)
toast.dismiss()(id?: string)void
toast.remove()(id?: string)void
toast.promise()<T>(promise | () => Promise, opts, toastOpts?)Promise<T>
toast.confirm()(message: string, opts?: ConfirmOptions)Promise<boolean>
toast.dismissGroup()(group: string)void
toast.removeGroup()(group: string)void
toast.pauseAll()()void
toast.resumeAll()()void
toast.history()()ReadonlyArray<ToastHistoryEntry>

Options Interface

ToastOptions

Configuration object accepted by all toast methods.

types.ts
typescript
interface ToastOptions {
  id?: string;
  title?: string;
  variant?: ToastVariant;
  duration?: number;
  position?: ToastPosition;
  dismissible?: boolean;
  removeDelay?: number;

  // v2 additions
  action?: ToastAction;
  cancelAction?: ToastAction;
  onDismiss?: () => void;
  group?: string;
  sound?: boolean;
  fallbackToNotification?: boolean;
}

The v2 fields cover the four new patterns: inline action buttons (action /cancelAction), batched dismissal (group), side effects on close (onDismiss), and the two opt-in attention paths (sound for in-page audio, fallbackToNotification for OS notifications when the tab is hidden). See their dedicated Usage Guide sections for runnable examples.

Promise Handling

PromiseOptions<T>

Configuration for toast.promise().

types.ts
typescript
interface PromiseOptions<T> {
  loading: string;
  success: string | ((data: T) => string);
  error: string | ((err: unknown) => string);

  // v2 — optional determinate progress reporter
  progress?: (report: (pct: number) => void) => void;
}

Pass a progressreporter to drive the toast's progress bar with a real 0..1 value (e.g. upload progress) instead of the default indeterminate animation.

Confirm Options

ConfirmOptionsv2

Second argument to toast.confirm(). All fields are optional.

types.ts
typescript
interface ConfirmOptions {
  confirmLabel?: ReactNode;   // default: "Confirm"
  cancelLabel?: ReactNode;    // default: "Cancel"
  variant?: ToastVariant;     // default: "warning"
  title?: string;
  position?: ToastPosition;
}

History Entry

ToastHistoryEntryv2

Shape of each entry returned by toast.history(). Read-only.

types.ts
typescript
interface ToastHistoryEntry {
  id: string;
  message: string | ReactNode;
  title?: string;
  variant: ToastVariant;     // "loading" is coerced to "info"
  dismissedAt: number;       // ms since epoch
  group?: string;
}

Type Aliases

Exported type aliases for use in your TypeScript code.

types.ts
typescript
// Primitive variant + position unions
type ToastVariant   = "success" | "error" | "warning" | "info" | "default";
type ToastPosition  =
  | "top-right" | "top-left" | "top-center"
  | "bottom-right" | "bottom-left" | "bottom-center";

// v2 additions
type ToastDirection = "ltr" | "rtl";
type ToastTheme     = "auto" | "light" | "dark";
type ToastLayout    = "default" | "stack";

// Importable from "toast-23"
import type {
  // Variants & geometry
  ToastVariant,
  ToastPosition,
  ToastDirection,
  ToastTheme,
  ToastLayout,

  // API surface
  ToastApi,
  ToastOptions,
  ToastAction,
  PromiseOptions,
  ConfirmOptions,
  ToastHistoryEntry,

  // Provider + components
  Toast23ProviderProps,
  Toast23DevToolsProps,

  // Headless
  HeadlessApi,

  // Standalone
  StandaloneOptions,
  StandaloneToastApi,
} from "toast-23";

Shape of the helper types

types.ts
typescript
interface ToastAction {
  label: ReactNode;
  onClick: (dismiss: () => void) => void;
  dismissOnClick?: boolean;
  className?: string;
}

interface ConfirmOptions {
  confirmLabel?: ReactNode;
  cancelLabel?: ReactNode;
  variant?: ToastVariant;
  title?: string;
  position?: ToastPosition;
}

interface ToastHistoryEntry {
  id: string;
  message: string | ReactNode;
  title?: string;
  variant: ToastVariant;
  dismissedAt: number;
  group?: string;
}

interface HeadlessApi {
  toasts: ReadonlyArray<InternalToast>;
  dismiss: (id?: string) => void;
  remove: (id?: string) => void;
  dismissGroup: (group: string) => void;
  removeGroup: (group: string) => void;
  pauseAll: () => void;
  resumeAll: () => void;
  history: () => ReadonlyArray<ToastHistoryEntry>;
  isPausedGlobally: boolean;
}
  • ToastAction.onClick receives a dismiss helper. Call it to close the toast manually; otherwise the toast auto-dismisses after the handler returns unless dismissOnClick is set to false.
  • ToastHistoryEntry.variant collapses "loading" entries down to "info"so history consumers don't have to deal with the transient loading state.
  • ToastHistoryEntry.dismissedAt is a millisecond timestamp from Date.now(). Use new Date(entry.dismissedAt) if you need a Date object.

Toast23DevToolsPropsv2

Props accepted by the optional <Toast23DevTools /> inspector panel.

types.ts
typescript
interface Toast23DevToolsProps {
  /**
   * Screen corner to dock the panel.
   * @default "bottom-left"
   */
  position?: "top-left" | "top-right" | "bottom-left" | "bottom-right";

  /**
   * Start in the small chip state (just the title + queue count).
   * The user can expand from the chip; this only controls initial render.
   * @default true
   */
  collapsed?: boolean;
}

See the Playground section for what the panel surfaces (Queue / History / Settings tabs, variant filters, replay, copy-as-JSON).

CSS Class Reference

All CSS classes used by toast-23. Override these to customize the look and feel.

ClassDescription
.toast23-containerFixed-position container
.toast23-container--{position}Position modifier (top-right, etc.)
.toast23-itemIndividual toast element
.toast23-item--{variant}Variant modifier (success, error, etc.)
.toast23-iconIcon wrapper
.toast23-contentMessage content area
.toast23-titleTitle heading
.toast23-messageMessage body text
.toast23-dismissDismiss button
.toast23-progressProgress bar (auto-dismiss indicator)
.toast23-queue-badgeQueued toast count indicator

Releases

Changelog for toast-23.

v2.0.0LatestMay 2026

Themes, sounds, and the confirm UX

  • +New theme prop on Toast23Provider for forcing light/dark when toasts portal outside an ancestor .dark wrapper
  • +Per-variant sound patches (multi-note, attack envelope, tuned to the perceptually-loud band). Opt-in via sound: true
  • +Confirm pattern (action + cancelAction) gets a new layout: stacked action row, dismiss × pinned to the corner
  • +Cancel button now renders as a filled neutral button instead of bare text
  • +Styles auto-inject. The provider and createToast23() insert the stylesheet into <head> on mount, so the explicit import "toast-23/styles.css" is no longer required. The subpath export still ships for consumers that want manual control (SSR-FOUC, cascade ordering, build-time CSS extraction)
  • Queue badge (+N more) tracks the active theme
  • Sound autoplay race fixed, so the first toast after a user gesture is no longer silent
  • RTL stays fully opt-in via the dir prop
v1.0.2February 2026

Patch & polish

  • Bug fixes and stability improvements over v1.0.1
  • Documentation polish across the API reference
  • Refined enter / exit transitions and progress-bar timing
v1.0.1January 2026

Initial Release

  • 5 toast variants: success, error, warning, info, default
  • 6 position options with per-toast override
  • Promise API with loading → success/error transitions
  • Loading toast shortcut
  • Custom JSX toasts with toast.custom()
  • Dismiss all / Remove instantly APIs
  • Update existing toasts & prevent duplicates via id
  • Hover-pause with smooth progress bar reversal
  • Dark mode (automatic + manual toggle)
  • Queue system with +N badge
  • Full TypeScript support
  • Accessible (ARIA live regions, keyboard support)
  • Zero dependencies (React peer dep only)
  • Standalone API for Angular, Vue, Svelte, vanilla JS
  • SSR compatible (Next.js, Remix, Gatsby, Astro)