Snackbars provide brief messages about app processes at the bottom of the screen.
An application should normally have a single Snackbar added near the root of
the application.
import { CoreProviders } from "@react-md/core/CoreProviders";
+import { Snackbar } from "@react-md/core/snackbar/Snackbar";
import rmdConfig from "./rmdConfig.js";
return (
<CoreProviders {...rmdConfig}>
{children}
+ <Snackbar />
</CoreProviders>
);
Once the Snackbar component has been included in the application, toasts
can be added by using the addToast function. Simple messages can be created
by providing the children as the toast content.
The default behavior for toasts:
"use client";
import { Button } from "@react-md/core/button/Button";
import { addToast } from "@react-md/core/snackbar/ToastManager";
import { type ReactElement } from "react";
export default function SimpleToastExample(): ReactElement {
return (
<Button
onClick={() => {
addToast({ children: "Hello, world!" });
}}
>
Add Toast
</Button>
);
}
The children for the toast can also be any renderable element and span
multiple lines.
"use client";
import { Button } from "@react-md/core/button/Button";
import { addToast } from "@react-md/core/snackbar/ToastManager";
import { type ReactElement } from "react";
export default function MultipleLinesToastExample(): ReactElement {
return (
<Button
onClick={() => {
addToast({
children: (
<>
<p>This a toast that has multiple lines.</p>
<p>Pretty exciting.</p>
</>
),
});
}}
>
Add Toast
</Button>
);
}
The Toast supports all the theme colors by setting the theme.
"use client";
import { Button } from "@react-md/core/button/Button";
import { addToast } from "@react-md/core/snackbar/ToastManager";
import { type ReactElement } from "react";
export default function ToastThemeExample(): ReactElement {
return (
<>
<Button
onClick={() => {
addToast({ children: "Surface Theme (Default)", theme: "surface" });
}}
themeType="contained"
>
Surface
</Button>
<Button
onClick={() => {
addToast({ children: "Primary Theme", theme: "primary" });
}}
theme="primary"
themeType="contained"
>
Primary
</Button>
<Button
onClick={() => {
addToast({ children: "Secondary Theme", theme: "secondary" });
}}
theme="secondary"
themeType="contained"
>
Secondary
</Button>
<Button
onClick={() => {
addToast({ children: "Success Theme", theme: "success" });
}}
theme="success"
themeType="contained"
>
Success
</Button>
<Button
onClick={() => {
addToast({ children: "Warning Theme", theme: "warning" });
}}
theme="warning"
themeType="contained"
>
Warning
</Button>
<Button
onClick={() => {
addToast({ children: "Error Theme", theme: "error" });
}}
theme="error"
themeType="contained"
>
Error
</Button>
</>
);
}
If the default duration of 5 seconds does not work for a specific toast, set the
visibleTime to a duration in milliseconds to wait before hiding the toast.
"use client";
import { Button } from "@react-md/core/button/Button";
import { addToast } from "@react-md/core/snackbar/ToastManager";
import { type ReactElement } from "react";
export default function CustomToastVisibleTimeExample(): ReactElement {
return (
<Button
onClick={() => {
addToast({
children: "Wait 3 seconds instead of 5",
visibleTime: 3000,
});
}}
>
Toast!
</Button>
);
}
If a toast should be able to be dismissed early, a close button can be added by
adding closeButton: true when creating the toast. The close button will
default to having an aria-label="Close" and using the
close icon.
"use client";
import { Button } from "@react-md/core/button/Button";
import { addToast } from "@react-md/core/snackbar/ToastManager";
import { type ReactElement } from "react";
export default function AddACloseButtonExample(): ReactElement {
return (
<Button
onClick={() => {
addToast({ children: "Message.", closeButton: true });
}}
>
Toast!
</Button>
);
}
If custom props are required for the close button, provide closeButtonProps
instead.
"use client";
import { Button } from "@react-md/core/button/Button";
import { addToast } from "@react-md/core/snackbar/ToastManager";
import { type ReactElement } from "react";
export default function CloseButtonPropsExample(): ReactElement {
return (
<Button
onClick={() => {
addToast({
children: "Message.",
closeButtonProps: {
theme: "warning",
themeType: "outline",
buttonType: "text",
children: "Close",
},
});
}}
>
Toast!
</Button>
);
}
A toast can be updated to have an action button appear to the right of the
message by providing an action object. It should normally have a simple
onClick event handler and children to display in the button.
"use client";
import { Button } from "@react-md/core/button/Button";
import { addToast } from "@react-md/core/snackbar/ToastManager";
import { type ReactElement } from "react";
export default function ActionableToastExample(): ReactElement {
return (
<Button
onClick={() => {
addToast({
children: "This toast has an optional action",
action: {
children: "Undo",
onClick: () => {
// do something
},
},
});
}}
>
Toast!
</Button>
);
}
Both an action and close button can be displayed together.
"use client";
import { Button } from "@react-md/core/button/Button";
import { addToast } from "@react-md/core/snackbar/ToastManager";
import { type ReactElement } from "react";
export default function ActionAndCloseButtonExample(): ReactElement {
return (
<Button
onClick={() => {
addToast({
children: "Message.",
closeButton: true,
action: {
onClick: () => {
// do something,
},
children: "Undo",
},
});
}}
>
Toast!
</Button>
);
}
If an action must be clicked to hide the toast, set the visibleTime to
null.
"use client";
import { Button } from "@react-md/core/button/Button";
import { addToast } from "@react-md/core/snackbar/ToastManager";
import { type ReactElement } from "react";
export default function RequireAnActionExample(): ReactElement {
return (
<Button
onClick={() => {
addToast({
children: "Message",
action: {
children: "Must click",
onClick: () => {
// do something
},
},
visibleTime: null,
});
}}
>
Toast!
</Button>
);
}
If the action is complex and requires additional behavior, provide an
actionButton instead.
"use client";
import { AsyncButton } from "@react-md/core/button/AsyncButton";
import { Button } from "@react-md/core/button/Button";
import { addToast } from "@react-md/core/snackbar/ToastManager";
import { useCurrentToastActions } from "@react-md/core/snackbar/useCurrentToastActions";
import { wait } from "@react-md/core/utils/wait";
import { type ReactElement } from "react";
export default function CustomActionButtonExample(): ReactElement {
return (
<Button
onClick={() => {
addToast({
children: "Something happened.",
actionButton: <ActionButton />,
visibleTime: null,
});
}}
>
Toast!
</Button>
);
}
function ActionButton(): ReactElement {
const { removeToast } = useCurrentToastActions();
return (
<AsyncButton
onClick={async () => {
await wait(5000);
removeToast(true);
}}
theme="secondary"
>
Undo
</AsyncButton>
);
}
If the action should be below the toast content instead of inline, enable the
stacked option when creating a toast.
"use client";
import { Button } from "@react-md/core/button/Button";
import { addToast } from "@react-md/core/snackbar/ToastManager";
import { type ReactElement } from "react";
export default function StackedToastExample(): ReactElement {
return (
<>
<Button
onClick={() => {
addToast({
children: "Hello, world!",
stacked: true,
action: "Action",
});
}}
>
Stacked
</Button>
<Button
onClick={() => {
addToast({
children: "Hello, world!",
stacked: true,
action: "Action",
closeButton: true,
});
}}
>
With Close Button
</Button>
</>
);
}
If multiple Snackbar need to be mounted at the same time, create a new
ToastManager and wrap the React tree with the ToastManagerProvider. Now
toasts should be created by calling manager.addToast or the addToast
returned by useAddToast.
This example will give a quick example and show how the Snackbar can be
updated to have toastDefaults.
Check out the custom toast renderer example to see a real use case for the custom toast manager.
"use client";
import { Button } from "@react-md/core/button/Button";
import { Snackbar } from "@react-md/core/snackbar/Snackbar";
import { ToastManager, addToast } from "@react-md/core/snackbar/ToastManager";
import {
ToastManagerProvider,
useAddToast,
} from "@react-md/core/snackbar/ToastManagerProvider";
import { type ReactElement } from "react";
const manager = new ToastManager();
export default function CustomToastManagerExample(): ReactElement {
return (
<ToastManagerProvider manager={manager}>
<Content />
<Snackbar
toastDefaults={{
theme: "secondary",
closeButton: true,
}}
/>
</ToastManagerProvider>
);
}
function Content(): ReactElement {
const currentAddToast = useAddToast();
return (
<>
<Button
onClick={() => {
currentAddToast({ children: "Context initiated toast." });
}}
>
Context Toast
</Button>
<Button
onClick={() => {
manager.addToast({ children: "Manager initiated toast." });
}}
>
Manager Toast
</Button>
<Button
onClick={() => {
addToast({ children: "Global toast.", closeButton: true });
}}
>
Global Toast
</Button>
</>
);
}
The following demos and features do not require a custom
ToastManager to work outside of this documentation site unless specifically
stated.
The default behavior allows multiple toasts with the same content to be added
into the queue because each toast gets a unique toastId generated by
nanoid when added into the queue. Since this
might not be ideal, toast timers can automatically restart if the same content
is added into the queue. Manually set the toastId when adding a toast to
enable this feature.
Check out the custom toast renderer example
for another use case for manually setting the toastId.
"use client";
import { AsyncButton } from "@react-md/core/button/AsyncButton";
import { Snackbar } from "@react-md/core/snackbar/Snackbar";
import { ToastManager } from "@react-md/core/snackbar/ToastManager";
import { ToastManagerProvider } from "@react-md/core/snackbar/ToastManagerProvider";
import { DEFAULT_SCALE_TIMEOUT } from "@react-md/core/transition/useScaleTransition";
import { type UseStateSetter } from "@react-md/core/types";
import { type ReactElement, useEffect, useState } from "react";
const TOAST_ID = "toast-id-1";
const manager = new ToastManager();
export default function PreventingDuplicateToastsExample(): ReactElement {
const [running, setRunning] = useMultipleToasts();
return (
<ToastManagerProvider manager={manager}>
<AsyncButton
loading={running}
onClick={() => {
setRunning(true);
manager.addToast({
toastId: TOAST_ID,
children: "Message 1",
});
}}
>
Toast!
</AsyncButton>
<Snackbar
toastDefaults={{
children: <ActiveTime />,
closeButton: true,
onExited: () => {
setRunning(false);
},
}}
/>
</ToastManagerProvider>
);
}
function useMultipleToasts(): [
running: boolean,
setRunning: UseStateSetter<boolean>,
] {
const [running, setRunning] = useState(false);
useEffect(() => {
if (!running) {
return;
}
let timeout = globalThis.setTimeout(() => {
manager.addToast({
toastId: TOAST_ID,
children: "This will replace the content!",
});
timeout = globalThis.setTimeout(() => {
// this will just reset the time
manager.addToast({ toastId: TOAST_ID });
timeout = globalThis.setTimeout(() => {
manager.addToast({
toastId: TOAST_ID,
children: "Replacing again, but no restart",
duplicates: "update",
});
}, 3000);
}, 3000);
}, 3000);
return () => {
globalThis.clearTimeout(timeout);
};
}, [running]);
return [running, setRunning];
}
function ActiveTime(): ReactElement {
const [time, setTime] = useState(0);
useEffect(() => {
let interval: NodeJS.Timeout;
const timeout = globalThis.setTimeout(() => {
interval = globalThis.setInterval(() => {
setTime((prevTime) => prevTime + 1);
}, 1000);
}, DEFAULT_SCALE_TIMEOUT.enter);
return () => {
globalThis.clearTimeout(timeout);
globalThis.clearInterval(interval);
};
}, []);
return <div>{`Visible for ${time} seconds`}</div>;
}
The Snackbar can be configured to display more than one toast at a time by
setting the limit prop.
Set the position prop on the Snackbar to update the position within the
viewport to one of the following: "bottom" (default), "bottom-left",
"bottom-right", "top", "top-left", or "top-right".
"use client";
import { Button } from "@react-md/core/button/Button";
import { Option } from "@react-md/core/form/Option";
import { Select } from "@react-md/core/form/Select";
import { Snackbar } from "@react-md/core/snackbar/Snackbar";
import { ToastManager } from "@react-md/core/snackbar/ToastManager";
import { ToastManagerProvider } from "@react-md/core/snackbar/ToastManagerProvider";
import { type SnackbarPosition } from "@react-md/core/snackbar/snackbarStyles";
import { type ReactElement, useState } from "react";
export default function SnackbarPositionExample(): ReactElement {
const [position, setPosition] = useState<SnackbarPosition>("bottom");
return (
<ToastManagerProvider manager={manager}>
<Button
onClick={() => {
manager.addToast({
children: "Hello, world!",
visibleTime: null,
});
}}
>
Toast!
</Button>
<Button
theme="warning"
themeType="outline"
onClick={() => {
manager.clearToasts();
}}
>
Remove all toasts
</Button>
<Select
label="Position"
value={position}
onChange={(event) => {
setPosition(event.currentTarget.value);
}}
>
{positions.map((position) => (
<Option key={position} value={position}>
{position}
</Option>
))}
</Select>
<Snackbar position={position} limit={3} />
</ToastManagerProvider>
);
}
const manager = new ToastManager();
const positions: readonly SnackbarPosition[] = [
"bottom",
"bottom-left",
"bottom-right",
"top",
"top-left",
"top-right",
];
The default toast queue priority is first-in-first-out so that new toasts are always added to the end of the queue. However, there are cases where a new toast needs to be shown immediately to the user since some event happened that causes the application to no longer work. A few examples are losing internet connection or API requests failing.
To change the insert order for a new toast, include the priority option which
can be set to one of the following:
"normal" (default) - the toast will be added to the end of the queue"next" - the toast will be inserted next-in-line in the queue, waiting for
the current visible toast to exit before being shown. If the toast does not
support duplicates, the existing toast will be moved instead and merged with
the toast."replace" - if there is a currently visible toast, it will start the leave
transition and display the newly added toast instead."immediate" - the same behavior as "replace" except that if there was a
currently visible toast, the toast will be shown again once the "immediate"
toast is hidden.This example will try to show this behavior while logging the current toast queue.
It is recommended to only look at this example for complex applications since it requires building most of the toast functionality manually and using some low-level components.
If the toasts in the application require additional business logic or a combination of complex components, it is recommended to create a custom toast renderer with an optional toast manager. Using both together is recommended since it is possible to enforce only known toasts by creating a custom toast manager and handle those specific toasts in the renderer.
This example will showcase how to create a toast system that:
DefaultToastRenderer to implement rendering a toast will the normal
visibility behaviorToastContent componenttoastIduseCurrentToastActions or useRemoveToast hooks"use client";
import { AsyncButton } from "@react-md/core/button/AsyncButton";
import { Button } from "@react-md/core/button/Button";
import { type BackgroundColor } from "@react-md/core/cssUtils";
import {
DefaultToastRenderer,
type ToastRendererProps,
} from "@react-md/core/snackbar/DefaultToastRenderer";
import { Snackbar } from "@react-md/core/snackbar/Snackbar";
import { ToastContent } from "@react-md/core/snackbar/ToastContent";
import { ToastManager } from "@react-md/core/snackbar/ToastManager";
import {
ToastManagerProvider,
useRemoveToast,
} from "@react-md/core/snackbar/ToastManagerProvider";
import { useCurrentToastActions } from "@react-md/core/snackbar/useCurrentToastActions";
import { wait } from "@react-md/core/utils/wait";
import { type ReactElement } from "react";
export default function CustomToastRendererExample(): ReactElement {
return (
<ToastManagerProvider manager={manager}>
<Button
onClick={() => {
addToast("Offline");
addToast("Success");
addToast("Undo");
addToast("Redo");
}}
>
Toast!
</Button>
<Snackbar renderToast={CustomToastRenderer} />
</ToastManagerProvider>
);
}
type ToastId = "Undo" | "Redo" | "Offline" | "Success";
const addToast = (toastId: ToastId): void => {
let theme: BackgroundColor = "surface";
let visibleTime: number | undefined | null;
let closeButton = true;
switch (toastId) {
case "Offline":
theme = "error";
// pretend like you have some logic to check online status
visibleTime = 10_000;
closeButton = false;
break;
case "Undo":
theme = "warning";
break;
case "Success":
theme = "success";
break;
}
manager.addToast({
toastId,
theme,
visibleTime,
closeButton,
});
};
function assertKnownToast(_toastId: string): asserts _toastId is ToastId {
// pretend assertion
}
function AsyncAction({ toastId }: { toastId: ToastId }): ReactElement {
// If the current `toastId` is not available for some reason, use the
// `removeToast` returned from `useCurrentToastActions` instead.
const removeToast = useRemoveToast();
const { pauseRemoveTimeout } = useCurrentToastActions();
return (
<AsyncButton
onClick={async () => {
pauseRemoveTimeout();
// pretend some API call or business logic
await wait(3000);
// Use `false` if the toast exit transition should not occur when
// removing the toast
removeToast(toastId, true);
}}
>
{toastId}
</AsyncButton>
);
}
function CustomToastRenderer(props: ToastRendererProps): ReactElement {
const { toastId } = props;
assertKnownToast(toastId);
return (
<DefaultToastRenderer {...props} disableToastContent>
<ToastContent>{toastId}</ToastContent>
{toastId !== "Offline" && toastId !== "Success" && (
<AsyncAction toastId={toastId} />
)}
</DefaultToastRenderer>
);
}
const manager = new ToastManager();