Skip to main content
react-md

Snackbar

Snackbars provide brief messages about app processes at the bottom of the screen.

Initialize Snackbar

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.jsx";


 return (
   <CoreProviders {...rmdConfig}>
     {children}
+    <Snackbar />
   </CoreProviders>
 );

Simple Toast

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>
  );
}

Press Enter to start editing.

Multiple Lines

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>
  );
}

Press Enter to start editing.

Toast Theme

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>
    </>
  );
}

Press Enter to start editing.

Custom Toast Visible Time

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>
  );
}

Press Enter to start editing.

Add a Close 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>
  );
}

Press Enter to start editing.

Close Button Props

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>
  );
}

Press Enter to start editing.

Actionable Toasts

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>
  );
}

Press Enter to start editing.

Action and Close 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>
  );
}

Press Enter to start editing.

Require an action

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>
  );
}

Press Enter to start editing.

Custom Action 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>
  );
}

Press Enter to start editing.

Stacked Toasts

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>
    </>
  );
}

Press Enter to start editing.

Custom Toast Manager

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>
    </>
  );
}

Press Enter to start editing.

The following demos and features do not require a custom ToastManager to work outside of this documentation site unless specifically stated.

Preventing Duplicate Toasts

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 = window.setTimeout(() => {
      manager.addToast({
        toastId: TOAST_ID,
        children: "This will replace the content!",
      });

      timeout = window.setTimeout(() => {
        // this will just reset the time
        manager.addToast({ toastId: TOAST_ID });

        timeout = window.setTimeout(() => {
          manager.addToast({
            toastId: TOAST_ID,
            children: "Replacing again, but no restart",
            duplicates: "update",
          });
        }, 3000);
      }, 3000);
    }, 3000);

    return () => {
      window.clearTimeout(timeout);
    };
  }, [running]);

  return [running, setRunning];
}

function ActiveTime(): ReactElement {
  const [time, setTime] = useState(0);

  useEffect(() => {
    let interval: number | undefined;
    const timeout = window.setTimeout(() => {
      interval = window.setInterval(() => {
        setTime((prevTime) => prevTime + 1);
      }, 1000);
    }, DEFAULT_SCALE_TIMEOUT.enter);

    return () => {
      window.clearTimeout(timeout);
      window.clearInterval(interval);
    };
  }, []);

  return <div>{`Visible for ${time} seconds`}</div>;
}

Press Enter to start editing.

Multiple Visible Toasts

The Snackbar can be configured to display more than one toast at a time by setting the limit prop.

"use client";

import { Button } from "@react-md/core/button/Button";
import { LinearProgress } from "@react-md/core/progress/LinearProgress";
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 ReactElement, useRef } from "react";

import styles from "./MultipleVisibleToastsExample.module.scss";

const manager = new ToastManager();
const { addToast } = manager;

export default function MultipleVisibleToastsExample(): ReactElement {
  const count = useRef(0);
  return (
    <ToastManagerProvider manager={manager}>
      <Button
        onClick={() => {
          count.current = 1;
          addToast({ children: "Hello, world!" });
        }}
      >
        Toast!
      </Button>
      <Snackbar
        limit={5}
        position="top-right"
        toastDefaults={{
          className: styles.toast,
          closeButton: true,
          onEntered: () => {
            if (count.current < 5) {
              count.current += 1;
              addToast({ children: "Hello, World!" });
            }
          },
          children: <Countdown />,
          action: "Dismiss",
        }}
      />
    </ToastManagerProvider>
  );
}

function Countdown(): ReactElement {
  return (
    <LinearProgress
      aria-label="Visible time"
      className={styles.progress}
      barClassName={styles.countdown}
    />
  );
}

Press Enter to start editing.

@use "everything";

.toast {
  position: relative;
  transform-origin: right;

  @include everything.rtl {
    transform-origin: left;
  }
}

.progress {
  @include everything.progress-set-var(
    color,
    everything.theme-get-var(secondary-color)
  );

  background-color: everything.$white;
  bottom: 0;
  left: 0;
  position: absolute;
  right: 0;
}

@keyframes countdown {
  0% {
    width: 100%;
  }

  100% {
    width: 0%;
  }
}

.countdown {
  will-change: width;

  &::before {
    animation: none;
  }

  &::after {
    animation: 5s linear everything.$scale-transition-enter-duration countdown;
  }

  :global(.rmd-toast--paused) :local &::after {
    animation-play-state: paused;
  }
}

Press Enter to start editing.

Snackbar Position

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",
];

Press Enter to start editing.

Toast Priority

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:

This example will try to show this behavior while logging the current toast queue.

[]
"use client";

import { Box } from "@react-md/core/box/Box";
import { AsyncButton } from "@react-md/core/button/AsyncButton";
import { Form } from "@react-md/core/form/Form";
import { Radio } from "@react-md/core/form/Radio";
import { useRadioGroup } from "@react-md/core/form/useRadioGroup";
import { Snackbar } from "@react-md/core/snackbar/Snackbar";
import {
  ToastManager,
  type ToastPriority,
} from "@react-md/core/snackbar/ToastManager";
import {
  ToastManagerProvider,
  useAddToast,
  useRemoveToast,
  useToastQueue,
} from "@react-md/core/snackbar/ToastManagerProvider";
import { wait } from "@react-md/core/utils/wait";
import { type ReactElement } from "react";

import styles from "./ToastPriorityExample.module.scss";

const manager = new ToastManager();

export default function ToastPriorityExample(): ReactElement {
  return (
    <ToastManagerProvider manager={manager}>
      <Content />
    </ToastManagerProvider>
  );
}

function Content(): ReactElement {
  const queue = useToastQueue();
  const addToast = useAddToast();
  const removeToast = useRemoveToast();
  const { value, getRadioProps } = useRadioGroup<ToastPriority>({
    name: "toastFlow",
    defaultValue: "immediate",
  });

  const addNextFlow = async (): Promise<void> => {
    addToast({ children: "Hello, world! (1)" });
    addToast({ children: "Hello, world! (2)" });

    await wait(3000);
    addToast({
      theme: "error",
      toastId: "500",
      children: "Internal Server Error",
      priority: "next",
    });
  };

  const addImmediateOrReplace = async (replace: boolean): Promise<void> => {
    const priority = replace ? "replace" : "immediate";
    await wait(2000);
    addToast({
      toastId: "offline",
      children: "Offline",
      theme: "error",
      priority,
      visibleTime: null,
      closeButton: false,
    });
    addToast({
      theme: "error",
      toastId: "500",
      children: "Internal Server Error",
    });
    addToast({
      theme: "error",
      toastId: "500",
      children: "Internal Server Error",
    });
    addToast({
      theme: "error",
      toastId: "500",
      children: "Internal Server Error",
    });

    await wait(8000);
    removeToast("offline", true);
  };

  return (
    <Form className={styles.form}>
      <Box stacked align="flex-start">
        <Radio {...getRadioProps("next")} label="Next" />
        <Radio {...getRadioProps("replace")} label="Replace" />
        <Radio {...getRadioProps("immediate")} label="Immediate" />
      </Box>
      <AsyncButton
        type="submit"
        loading={queue.length > 0}
        onClick={async () => {
          addToast({ children: "Hello, world!" });

          if (value === "next") {
            return addNextFlow();
          }

          return addImmediateOrReplace(value === "replace");
        }}
        theme="primary"
      >
        Start Example
      </AsyncButton>
      <Snackbar />
      <pre className={styles.block}>
        <code className={styles.code}>{JSON.stringify(queue, null, 2)}</code>
      </pre>
    </Form>
  );
}

Press Enter to start editing.

.form {
  max-width: 30rem;
  width: 100%;
}

.code {
  height: 100%;
}

.block {
  height: 30rem;
  overflow: auto;
}

Press Enter to start editing.

Custom Toast Renderer

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:

"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 = 10000;
      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();

Press Enter to start editing.