Toast
Toast provides event-driven notifications for status updates, processing states, and user feedback. It renders dismissable banners in a fixed overlay and manages multiple toasts with stacking, live updates, and optional auto-dismiss.
Loading...
Overview
Resources
Install
yarn add @camp/toastUpgrading to Next Gen
From AI Toast (@camp/ai-banner)
The API is identical — only the import path and component name have changed.
Before
import { AiToaster, toast, ToastDurationPreset } from '@camp/ai-banner';
function App() {
return (
<>
{/* ...your app... */}
<AiToaster headerOffset="60px" />
</>
);
}After
import { Toaster, toast, ToastDurationPreset } from '@camp/toast';
function App() {
return (
<>
{/* ...your app... */}
<Toaster headerOffset="60px" />
</>
);
}🔄 Changed:
AI Toast (@camp/ai-banner) | Next Gen Toast (@camp/toast) |
|---|---|
AiToaster | Toaster |
import { ... } from '@camp/ai-banner' | import { ... } from '@camp/toast' |
All other exports — toast, showToast, updateToast, dismissToast, clearToasts, ToastDurationPreset — are unchanged.
From Camp 1 (@activecampaign/camp-components-toast)
The Next Gen API is a significant improvement: toast() returns a controller object so you can update or dismiss a toast without tracking IDs manually. Several prop names have also changed.
Setting up the Toaster
The toastDismissButtonLabel prop is no longer required on <Toaster>. Pass dismissLabel per individual toast instead if you need a custom label.
// Before (Camp 1)
import { Toaster } from '@activecampaign/camp-components-toast';
<Toaster toastDismissButtonLabel="Dismiss" headerOffset="60px" />;
// After (Next Gen)
import { Toaster } from '@camp/toast';
<Toaster headerOffset="60px" />;Creating a toast
createToast() is replaced by toast(), which returns a controller. duration is now optional (omit or pass 0 for a persistent toast).
// Before (Camp 1)
import { createToast, ToastDuration } from '@activecampaign/camp-components-toast';
const id = createToast({
title: 'Changes saved',
appearance: 'success',
duration: ToastDuration.Short,
});
// After (Next Gen)
import { toast, ToastDurationPreset } from '@camp/toast';
const controller = toast({
title: 'Changes saved',
appearance: 'success',
duration: ToastDurationPreset.Short,
});Dismissing a toast
// Before (Camp 1)
import { destroyToast } from '@activecampaign/camp-components-toast';
destroyToast(id);
// After (Next Gen) — via controller
controller.dismiss();
// After (Next Gen) — via store function (when you only have the id)
import { dismissToast } from '@camp/toast';
dismissToast(id);Updating a toast (new in Next Gen)
Camp 1 had no way to update an existing toast. Next Gen lets you update title, description, appearance, and duration in place:
import { toast, ToastDurationPreset } from '@camp/toast';
const controller = toast({
title: 'Saving…',
appearance: 'info',
duration: 0,
});
controller.update({
title: 'Saved',
appearance: 'success',
duration: ToastDurationPreset.Short,
});Adding actions
The actions prop is renamed to renderActions.
// Before (Camp 1)
createToast({
title: 'Error saving',
appearance: 'destructive',
actions: () => <Button size="small">Try again</Button>,
});
// After (Next Gen)
toast({
title: 'Error saving',
appearance: 'destructive',
renderActions: () => <Button size="small">Try again</Button>,
});Custom dismiss handler
onDismiss now receives the toast id in both APIs, but in Next Gen you call dismissToast(id) (instead of destroyToast(id)) inside your handler.
// Before (Camp 1)
import { createToast, destroyToast } from '@activecampaign/camp-components-toast';
createToast({
title: 'My notification',
onDismiss: (id) => {
// custom logic…
destroyToast(id);
},
dismissLabel: 'Dismiss',
});
// After (Next Gen)
import { toast, dismissToast } from '@camp/toast';
toast({
title: 'My notification',
onDismiss: (id) => {
// custom logic…
dismissToast(id);
},
dismissLabel: 'Dismiss',
});Prop changes summary
| Camp 1 prop / export | Next Gen equivalent | Notes |
|---|---|---|
createToast(props) | toast(props) | Returns a controller instead of a plain id |
destroyToast(id) | dismissToast(id) / controller.dismiss() | Renamed |
actions | renderActions | Same signature: () => ReactNode |
ToastDuration | ToastDurationPreset | Same string values: short, standard, long |
<Toaster toastDismissButtonLabel="..." /> | toast({ dismissLabel: '...' }) | Moved from Toaster to per-toast |
| — | toast().update(partial) | New: live-update an existing toast |
| — | clearToasts() | New: dismiss all toasts at once |
Variations
Basic Toast
Use the toast() controller to create, update, and dismiss toasts from component or hook code. It returns a controller with id, update, and dismiss methods.
import { Toaster, toast, ToastDurationPreset } from '@camp/toast';
const controller = toast({
title: 'Processing',
description: 'Your request is being handled.',
appearance: 'info',
duration: ToastDurationPreset.Standard,
});
// Update the same toast in place
controller.update({
title: 'Complete',
description: 'Your request was processed successfully.',
appearance: 'success',
duration: ToastDurationPreset.Standard,
});
// Dismiss when done
controller.dismiss();Stacked Toasts
Multiple toasts stack automatically in the top-right corner. Each toast is independently dismissable and auto-dismisses based on its configured duration.
import { Toaster, toast, ToastDurationPreset } from '@camp/toast';
toast({
title: 'Info notice',
description: 'Heads up',
appearance: 'info',
duration: ToastDurationPreset.Long,
});
toast({
title: 'Warning',
description: 'Check settings',
appearance: 'warning',
duration: ToastDurationPreset.Standard,
});
toast({ title: 'Success', description: 'Saved', appearance: 'success', duration: 3000 });Updating Toast Content
Update a toast in place while an async operation is in progress — useful for uploads, API calls, or multi-step workflows.
import { Toaster, toast, ToastDurationPreset } from '@camp/toast';
const controller = toast({
title: 'Processing…',
description: 'Please wait.',
appearance: 'info',
duration: 0, // persistent until updated
});
setTimeout(() => {
controller.update({
title: 'Complete',
description: 'All done.',
appearance: 'success',
duration: ToastDurationPreset.Standard,
});
}, 3000);Multiple Toasts with Targeted Updates
Use separate controllers to independently update multiple concurrent toasts.
import { Toaster, toast, ToastDurationPreset } from '@camp/toast';
const syncA = toast({
title: 'Syncing A…',
appearance: 'info',
duration: ToastDurationPreset.Long,
});
const syncB = toast({
title: 'Syncing B…',
appearance: 'info',
duration: ToastDurationPreset.Long,
});
syncA.update({ title: 'A Complete', appearance: 'success', duration: 4000 });
syncB.update({
title: 'B Error',
description: 'Something went wrong',
appearance: 'destructive',
duration: ToastDurationPreset.Standard,
});Toast with Progress Bar
The title and description props accept React nodes, allowing you to embed rich content like progress bars or formatted text.
import { Toaster, toast } from '@camp/toast';
import { ProgressBar } from '@camp/progress-bar';
import { useEffect, useRef } from 'react';
function UploadComponent() {
const controllerRef = useRef<ReturnType<typeof toast> | null>(null);
useEffect(() => {
controllerRef.current = toast({
title: 'Uploading…',
description: (
<div style={{ width: 320, marginTop: 8 }}>
<ProgressBar value={1} ariaLabel="Upload progress" showValue />
</div>
),
appearance: 'info',
duration: 0,
});
const timer = setTimeout(() => {
controllerRef.current?.update({
title: 'Upload complete',
description: (
<div style={{ width: 320, marginTop: 8 }}>
<ProgressBar value={100} appearance="success" ariaLabel="Upload progress" showValue />
</div>
),
appearance: 'success',
duration: 3000,
});
}, 2000);
return () => clearTimeout(timer);
}, []);
return <Toaster />;
}Store API
For cross-boundary scenarios — outside React components, background jobs, or global cleanup — use the low-level store functions directly.
import {
showToast,
updateToast,
dismissToast,
clearToasts,
ToastDurationPreset,
} from '@camp/toast';
// Create a toast and capture the id
const id = showToast({
title: 'Processing started',
description: 'Analyzing data…',
appearance: 'info',
duration: 0,
});
// Update it later using the id
updateToast(id, {
title: 'Processing complete',
appearance: 'success',
duration: ToastDurationPreset.Standard,
});
// Dismiss a specific toast, or clear all
dismissToast(id);
clearToasts();Usage
Setup
Render <Toaster /> once near your app root. It requires a ThemeProvider and uses a portal to render toasts at the top of document.body. Use headerOffset to push the container below a fixed header.
import { Toaster } from '@camp/toast';
import { ThemeProvider } from 'styled-components';
import { campTheme } from '@camp/theme';
function App() {
return (
<ThemeProvider theme={campTheme}>
{/* ...your app... */}
<Toaster headerOffset="60px" />
</ThemeProvider>
);
}See the Styled Components & ThemeProvider guide for full setup instructions.
Controller API vs Store API
Use the Controller API (toast()) for typical component and hook code — it keeps the toast lifecycle localized without tracking IDs across modules:
import { toast, ToastDurationPreset } from '@camp/toast';
const controller = toast({
title: 'Settings updated',
appearance: 'success',
duration: ToastDurationPreset.Standard,
});
controller.update({ title: 'Refreshing…', appearance: 'info', duration: 0 });
controller.dismiss();Use the Store API (showToast, updateToast, dismissToast, clearToasts) when you need to trigger or update toasts from outside React — for example in service files, background jobs, or global error handlers.
Duration Presets
Use ToastDurationPreset for consistent timing across the application. Pass duration: 0 (or omit it) for a persistent toast that the user must dismiss manually.
| Preset | Duration |
|---|---|
ToastDurationPreset.Short | 4 seconds |
ToastDurationPreset.Standard | 8 seconds |
ToastDurationPreset.Long | 10 seconds |
Best Practices
- Use
appearance: 'info'while an operation is in progress; update to'success','warning', or'destructive'once the outcome is known. - Use persistent toasts (
duration: 0) only when user action is required before dismissal. - Prefer updating an existing toast over creating multiple toasts for the same operation.
- Clean up persistent toasts on navigation if they are no longer relevant.
- Keep toast content concise — one or two sentences at most.
Content Guidelines
✅ DO
Update an in-progress toast to reflect the outcome when an async operation completes.
🚫 DON’T
Create a new toast for every step of a multi-step operation. Update the existing one instead.
✅ DO
Use duration presets for consistent timing across the application.
🚫 DON’T
Accessibility
- Each toast renders with
role="alert", so screen readers announce it immediately on creation. - Dismiss buttons have an accessible label via the
dismissLabelprop (falls back to a default close label). - Action buttons rendered via
renderActionsare reachable through normal tab order. - Persistent toasts (
duration: 0) remain visible until dismissed, giving screen reader users sufficient time to interact with the content.
