Browse docs
Browse docs
Imperative toast notifications. You don't render <Snackbar> yourself — you mount the <SnackbarProvider> once at app root and call enqueue({ ... }) from anywhere via useSnackbar(). The stack anchors to one of six corners, capped at maxVisible simultaneously visible items (extras queue FIFO).
De-duplication by id is built-in: enqueueing the same id twice in a row reuses the existing snackbar and resets its auto-dismiss timer — handy for "Saved" toasts triggered repeatedly.
import { useSnackbar } from '@dashforge/tw';
function SaveButton({ draftId }: { draftId: string }) {
const { enqueue } = useSnackbar();
async function handleSave() {
try {
await api.save(draftId);
enqueue({ message: 'Saved', severity: 'success' });
} catch (err) {
enqueue({
message: 'Could not save — try again',
severity: 'danger',
autoHideMs: 0, // persistent until user dismisses
action: { label: 'Retry', onClick: handleSave },
});
}
}
return <Button onClick={handleSave}>Save</Button>;
}Typically at the app root, inside your theme provider:
import { DashforgeTailwindProvider } from '@dashforge/tw-theme';
import { SnackbarProvider } from '@dashforge/tw';
createRoot(document.getElementById('root')!).render(
<DashforgeTailwindProvider>
<SnackbarProvider position="bottom-right" maxVisible={3}>
<App />
</SnackbarProvider>
</DashforgeTailwindProvider>
);useSnackbar() from any componentimport { useSnackbar } from '@dashforge/tw';
const { enqueue, dismiss, dismissAll } = useSnackbar();
enqueue({ message: 'Profile updated', severity: 'success' });import { SnackbarProvider, useSnackbar, Button, Stack } from '@dashforge/tw';
function MyApp() {
const { enqueue } = useSnackbar();
return (
<Stack direction="row" gap={2}>
<Button onClick={() => enqueue({ message: 'Profile updated', severity: 'success' })}>
Show success
</Button>
<Button color="warning" onClick={() => enqueue({ message: 'Connection unstable', severity: 'warning' })}>
Show warning
</Button>
<Button color="danger" onClick={() => enqueue({ message: 'Upload failed', severity: 'danger' })}>
Show danger
</Button>
</Stack>
);
}
// Mount the provider once at app root:
<SnackbarProvider position="bottom-right">
<MyApp />
</SnackbarProvider>enqueue({ message: 'Heads up — read-only mode', severity: 'info' });
enqueue({ message: 'Workspace deleted', severity: 'success' });
enqueue({ message: 'Connection unstable', severity: 'warning' });
enqueue({ message: 'Upload failed', severity: 'danger' });severity drives the icon + accent color. Default is info.
For "do-something-about-it" toasts (Undo, Retry, View, …):
enqueue({
message: 'Message archived',
action: {
label: 'Undo',
onClick: () => api.unarchive(messageId),
},
});The action button is rendered inline. Clicking it does NOT auto-dismiss the snackbar — call dismiss(id) from inside onClick if you want both.
const id = enqueue({
message: 'Reconnecting…',
autoHideMs: 0, // 0 or negative ⇒ persistent
});
// Later, when reconnection succeeds:
dismiss(id);Default autoHideMs is 4000. Set 0 (or negative) for "stays until dismissed."
idCalling enqueue with the same id while one is already visible reuses the existing snackbar and resets its auto-dismiss timer:
// User mashes the save button 5x in a row:
function onSave() {
api.save();
enqueue({ id: 'saved-toast', message: 'Saved', severity: 'success' });
// ⇒ one snackbar appears, timer resets on each click instead of stacking 5 toasts
}Without id, each call stacks a new snackbar.
<SnackbarProvider position="top-center"> {/* top: left | center | right */}
<SnackbarProvider position="bottom-right"> {/* bottom: left | center | right — default */}Six anchors total: top-left · top-center · top-right · bottom-left · bottom-center · bottom-right.
Set common defaults once at the provider — useful when your product convention is e.g. "all toasts persistent, severity warning" or "always show close button":
<SnackbarProvider
position="bottom-center"
maxVisible={3}
defaults={{
severity: 'info',
autoHideMs: 6000,
showClose: true,
}}
>
<App />
</SnackbarProvider>Per-call enqueue() options always override provider defaults.
const { enqueue, dismiss, dismissAll } = useSnackbar();
const id = enqueue({ message: 'Uploading…', autoHideMs: 0 });
// later
dismiss(id);
// or, on route change:
useEffect(() => () => dismissAll(), [pathname]);<SnackbarProvider> props| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | — | App tree to wrap. Required. |
position | SnackbarPosition | 'bottom-right' | Corner the stack anchors to. |
maxVisible | number | 5 | Hard cap on simultaneously visible snackbars. Extras wait FIFO. |
defaults | Pick<SnackbarOptions, 'severity' | 'autoHideMs' | 'showClose'> | — | Defaults merged into every enqueue. |
slotProps | SnackbarSlotProps | — | Per-slot overrides applied to every snackbar. |
useSnackbar() returns SnackbarApitype SnackbarApi = {
/** Returns the assigned id (auto-generated if not provided). */
enqueue: (options: SnackbarOptions) => string;
dismiss: (id: string) => void;
dismissAll: () => void;
};
type SnackbarOptions = {
message: ReactNode;
severity?: 'info' | 'success' | 'warning' | 'danger'; // default 'info'
autoHideMs?: number; // default 4000; 0 ⇒ persistent
action?: { label: ReactNode; onClick: () => void };
showClose?: boolean; // default true
id?: string; // for de-duplication
};container · item · icon · message · action · closeButton
maxVisible snackbars are rendered at any time. If maxVisible is 3 and you enqueue 5 in quick succession, snackbars 4–5 wait in queue and slide in as 1–3 dismiss. Prevents the "wall of toasts" UX failure mode.id: enqueueing the same id while it's visible reuses the snackbar and resets the auto-dismiss timer (rather than stacking duplicates). If id is omitted, an auto-incrementing one is generated and every call stacks a new toast.autoHideMs: 0) require manual dismissal via the close button or dismiss(id). Use sparingly — persistent toasts that pile up are user-hostile. Reserve for "connection lost," "save failed," etc.onClick but leaves the snackbar visible. If you want both, call dismiss(id) from inside onClick — the id returned by enqueue() lets you do this cleanly.<ConfirmDialog> — snackbars are non-blocking and easy to miss; destructive confirms must be blocking.