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 install toast-23yarn add toast-23pnpm add toast-23bun add toast-23Quick 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.
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.
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
| Variant | Method | Use Case |
|---|---|---|
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.
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
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:
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:
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.
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:
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:
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:
// 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:
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.
| Type | Duration |
|---|---|
| success | 5000ms (provider default) |
| error | 5000ms (provider default) |
| warning | 5000ms (provider default) |
| info | 5000ms (provider default) |
| default | 5000ms (provider default) |
| loading | Infinity (persistent) |
| custom | 5000ms (provider default) |
// 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):
const toastId = toast.loading("Loading...");
// Later...
toast.dismiss(toastId);Dismiss All Toasts
Omit the id to dismiss all visible toasts at once:
toast.dismiss();Remove Toasts Instantly
To remove toasts instantly without any exit animation, use toast.remove():
// 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:
// 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:
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:
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.
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.
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.
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-dismissesToast 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.
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.
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.
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.
interface ToastHistoryEntry {
id: string;
message: string | ReactNode;
title?: string;
variant: ToastVariant;
dismissedAt: number;
group?: string;
}- ●
variantcollapses"loading"down to"info", so consumers never see the transient loading state in history. - ●
dismissedAtis a millisecond timestamp fromDate.now(). Wrap it withnew 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.
<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.
<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.
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.
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.
<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
<Toast23Provider fallbackToNotification>
<App />
</Toast23Provider>Enable per-toast
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 notitleoption 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.
<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.
<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.).
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
<Toast23Provider sound>
<App />
</Toast23Provider>Enable per-toast
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.
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.
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.
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:
// 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:
// 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:
// 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:
.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:
.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:
.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:
// 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
Works with both the App Router and Pages Router. Add the provider and stylesheet in your root layout or _app.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
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 & 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.
---
// src/pages/index.astro
---
<html>
<body>
<ToastWrapper client:load />
</body>
</html>// 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.
// Install peer dependencies
npm install react react-dom toast-23
npm install -D @types/react @types/react-dom// 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();
}
}// 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.
// Install peer dependencies
npm install react react-dom toast-23
npm install -D @types/react @types/react-dom// 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
import { createApp } from 'vue';
import App from './App.vue';
import { toast23Plugin } from './plugins/toast';
createApp(App).use(toast23Plugin).mount('#app');<!-- 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.
import { Toast23Provider } from "toast-23";
<Toast23Provider
position="top-right"
maxVisible={5}
duration={5000}
>
<App />
</Toast23Provider>| Prop | Type | Default | Description |
|---|---|---|---|
| children | ReactNode | — | Your application content (required). |
| position | ToastPosition | "top-right" | Default screen corner for toasts. |
| maxVisible | number | 5 | Maximum simultaneously visible toasts. |
| duration | number | 5000 | Default 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. |
| target | HTMLElement | null | null | Where to render the portal. Pass an element for inline mode. |
| historySize | number | 50 | Dismissed toasts to retain for `toast.history()`. |
| focusShortcut | string | null | "F8" | Keyboard shortcut to focus the toast container. `null` disables. |
| swipeEnabled | boolean | true | Enable swipe-to-dismiss for pointer + touch. |
| swipeThreshold | number | 80 | Pixel distance past which a swipe dismisses. |
| sound | boolean | false | Default `sound` option applied to every toast. |
| fallbackToNotification | boolean | false | Default fallback to OS notifications when the tab is hidden. |
Toast Hook
useToast()
React hook that returns the ToastApi. Must be called inside a <Toast23Provider>.
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.
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();| Option | Type | Default | Description |
|---|---|---|---|
| position | ToastPosition | "top-right" | Default screen corner for toasts. |
| maxVisible | number | 5 | Maximum simultaneously visible toasts. |
| duration | number | 5000 | Default 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. |
| historySize | number | 50 | Dismissed toasts to retain for `.history()`. |
| sound | boolean | false | Default `sound` option applied to every toast. |
| fallbackToNotification | boolean | false | Default fallback to OS notifications when the tab is hidden. |
| swipeEnabled | boolean | true | Enable swipe-to-dismiss for pointer + touch. |
| swipeThreshold | number | 80 | Pixel 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.
import { Toast23DevTools } from "toast-23";
{process.env.NODE_ENV !== "production" && (
<Toast23DevTools position="bottom-right" />
)}Props
| Prop | Type | Default | Description |
|---|---|---|---|
| position | "top-left" | "top-right" | "bottom-left" | "bottom-right" | "bottom-left" | Screen corner to dock the panel. |
| collapsed | boolean | true | Start 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.
| Method | Signature | Returns |
|---|---|---|
| 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.
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().
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.
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.
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.
// 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
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.onClickreceives adismisshelper. Call it to close the toast manually; otherwise the toast auto-dismisses after the handler returns unlessdismissOnClickis set tofalse. - ●
ToastHistoryEntry.variantcollapses"loading"entries down to"info"so history consumers don't have to deal with the transient loading state. - ●
ToastHistoryEntry.dismissedAtis a millisecond timestamp fromDate.now(). Usenew Date(entry.dismissedAt)if you need a Date object.
Toast23DevToolsPropsv2
Props accepted by the optional <Toast23DevTools /> inspector panel.
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.
| Class | Description |
|---|---|
| .toast23-container | Fixed-position container |
| .toast23-container--{position} | Position modifier (top-right, etc.) |
| .toast23-item | Individual toast element |
| .toast23-item--{variant} | Variant modifier (success, error, etc.) |
| .toast23-icon | Icon wrapper |
| .toast23-content | Message content area |
| .toast23-title | Title heading |
| .toast23-message | Message body text |
| .toast23-dismiss | Dismiss button |
| .toast23-progress | Progress bar (auto-dismiss indicator) |
| .toast23-queue-badge | Queued toast count indicator |
Releases
Changelog for toast-23.
Themes, sounds, and the confirm UX
- +New
themeprop onToast23Providerfor forcing light/dark when toasts portal outside an ancestor.darkwrapper - +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 explicitimport "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
dirprop
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
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)