alpha

Toast

A Toast displays a brief, temporary notification of actions, errors, or other events in an application.

installyarn add react-aria-components
version1.7.0
usageimport {ToastRegion, Toast} from 'react-aria-components'

Example#


First, render a ToastRegion in the root of your app.

import {Button, Text, UNSTABLE_Toast as Toast, UNSTABLE_ToastContent as ToastContent, UNSTABLE_ToastQueue as ToastQueue, UNSTABLE_ToastRegion as ToastRegion} from 'react-aria-components';

// Define the type for your toast content.
interface MyToastContent {
  title: string;
  description?: string;
}

// Create a global ToastQueue.
export const queue = new ToastQueue<MyToastContent>();

// Render a <ToastRegion> in the root of your app.
export function App() {
  return (
    <>
      <ToastRegion queue={queue}>
        {({ toast }) => (
          <Toast toast={toast}>
            <ToastContent>
              <Text slot="title">{toast.content.title}</Text>
              <Text slot="description">{toast.content.description}</Text>
            </ToastContent>
            <Button slot="close">x</Button>
          </Toast>
        )}
      </ToastRegion>
      {/* Your app here */}
    </>
  );
}
import {
  Button,
  Text,
  UNSTABLE_Toast as Toast,
  UNSTABLE_ToastContent as ToastContent,
  UNSTABLE_ToastQueue as ToastQueue,
  UNSTABLE_ToastRegion as ToastRegion
} from 'react-aria-components';

// Define the type for your toast content.
interface MyToastContent {
  title: string;
  description?: string;
}

// Create a global ToastQueue.
export const queue = new ToastQueue<MyToastContent>();

// Render a <ToastRegion> in the root of your app.
export function App() {
  return (
    <>
      <ToastRegion queue={queue}>
        {({ toast }) => (
          <Toast toast={toast}>
            <ToastContent>
              <Text slot="title">
                {toast.content.title}
              </Text>
              <Text slot="description">
                {toast.content.description}
              </Text>
            </ToastContent>
            <Button slot="close">x</Button>
          </Toast>
        )}
      </ToastRegion>
      {/* Your app here */}
    </>
  );
}
import {
  Button,
  Text,
  UNSTABLE_Toast
    as Toast,
  UNSTABLE_ToastContent
    as ToastContent,
  UNSTABLE_ToastQueue
    as ToastQueue,
  UNSTABLE_ToastRegion
    as ToastRegion
} from 'react-aria-components';

// Define the type for your toast content.
interface MyToastContent {
  title: string;
  description?: string;
}

// Create a global ToastQueue.
export const queue =
  new ToastQueue<
    MyToastContent
  >();

// Render a <ToastRegion> in the root of your app.
export function App() {
  return (
    <>
      <ToastRegion
        queue={queue}
      >
        {({ toast }) => (
          <Toast
            toast={toast}
          >
            <ToastContent>
              <Text slot="title">
                {toast
                  .content
                  .title}
              </Text>
              <Text slot="description">
                {toast
                  .content
                  .description}
              </Text>
            </ToastContent>
            <Button slot="close">
              x
            </Button>
          </Toast>
        )}
      </ToastRegion>
      {/* Your app here */}
    </>
  );
}

Then, you can trigger a toast from anywhere using the exported queue.

<Button
  onPress={() => queue.add({
    title: 'Toast complete!',
    description: 'Great success.'
  })}>
  Toast
</Button>
<Button
  onPress={() => queue.add({
    title: 'Toast complete!',
    description: 'Great success.'
  })}>
  Toast
</Button>
<Button
  onPress={() =>
    queue.add({
      title:
        'Toast complete!',
      description:
        'Great success.'
    })}
>
  Toast
</Button>
Show CSS
@import "@react-aria/example-theme";

.react-aria-ToastRegion {
  position: fixed;
  bottom: 16px;
  right: 16px;
  display: flex;
  flex-direction: column-reverse;
  gap: 8px;
  border-radius: 8px;
  outline: none;

  &[data-focus-visible] {
    outline: 2px solid slateblue;
    outline-offset: 2px;
  }
}

.react-aria-Toast {
  display: flex;
  align-items: center;
  gap: 16px;
  background: slateblue;
  color: white;
  padding: 12px 16px;
  border-radius: 8px;
  outline: none;

  &[data-focus-visible] {
    outline: 2px solid slateblue;
    outline-offset: 2px;
  }

  .react-aria-ToastContent {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-width: 0px;

    [slot=title] {
      font-weight: bold;
    }
  }

  .react-aria-Button[slot=close] {
    flex: 0 0 auto;
    background: none;
    border: none;
    appearance: none;
    border-radius: 50%;
    height: 32px;
    width: 32px;
    font-size: 16px;
    border: 1px solid white;
    color: white;
    padding: 0;
    outline: none;

    &[data-focus-visible] {
      box-shadow: 0 0 0 2px slateblue, 0 0 0 4px white;
    }

    &[data-pressed] {
      background: rgba(255, 255, 255, 0.2);
    }
  }
}
@import "@react-aria/example-theme";

.react-aria-ToastRegion {
  position: fixed;
  bottom: 16px;
  right: 16px;
  display: flex;
  flex-direction: column-reverse;
  gap: 8px;
  border-radius: 8px;
  outline: none;

  &[data-focus-visible] {
    outline: 2px solid slateblue;
    outline-offset: 2px;
  }
}

.react-aria-Toast {
  display: flex;
  align-items: center;
  gap: 16px;
  background: slateblue;
  color: white;
  padding: 12px 16px;
  border-radius: 8px;
  outline: none;

  &[data-focus-visible] {
    outline: 2px solid slateblue;
    outline-offset: 2px;
  }

  .react-aria-ToastContent {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-width: 0px;

    [slot=title] {
      font-weight: bold;
    }
  }

  .react-aria-Button[slot=close] {
    flex: 0 0 auto;
    background: none;
    border: none;
    appearance: none;
    border-radius: 50%;
    height: 32px;
    width: 32px;
    font-size: 16px;
    border: 1px solid white;
    color: white;
    padding: 0;
    outline: none;

    &[data-focus-visible] {
      box-shadow: 0 0 0 2px slateblue, 0 0 0 4px white;
    }

    &[data-pressed] {
      background: rgba(255, 255, 255, 0.2);
    }
  }
}
@import "@react-aria/example-theme";

.react-aria-ToastRegion {
  position: fixed;
  bottom: 16px;
  right: 16px;
  display: flex;
  flex-direction: column-reverse;
  gap: 8px;
  border-radius: 8px;
  outline: none;

  &[data-focus-visible] {
    outline: 2px solid slateblue;
    outline-offset: 2px;
  }
}

.react-aria-Toast {
  display: flex;
  align-items: center;
  gap: 16px;
  background: slateblue;
  color: white;
  padding: 12px 16px;
  border-radius: 8px;
  outline: none;

  &[data-focus-visible] {
    outline: 2px solid slateblue;
    outline-offset: 2px;
  }

  .react-aria-ToastContent {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-width: 0px;

    [slot=title] {
      font-weight: bold;
    }
  }

  .react-aria-Button[slot=close] {
    flex: 0 0 auto;
    background: none;
    border: none;
    appearance: none;
    border-radius: 50%;
    height: 32px;
    width: 32px;
    font-size: 16px;
    border: 1px solid white;
    color: white;
    padding: 0;
    outline: none;

    &[data-focus-visible] {
      box-shadow: 0 0 0 2px slateblue, 0 0 0 4px white;
    }

    &[data-pressed] {
      background: rgba(255, 255, 255, 0.2);
    }
  }
}

Features#


There is no built in way to display toast notifications in HTML. <ToastRegion> and <Toast> help achieve accessible toasts that can be styled as needed.

  • Accessible – Toasts follow the ARIA alertdialog pattern. They are rendered in a landmark region, which keyboard and screen reader users can easily jump to when an alert is announced.
  • Focus management – When a toast unmounts, focus is moved to the next toast if any. Otherwise, focus is restored to where it was before navigating to the toast region.

Anatomy#


Analysis completeTitleClose buttonToastRegion

A <ToastRegion> is an ARIA landmark region labeled "Notifications" by default. A <ToastRegion> accepts a function to render one or more visible toasts, in chronological order. When the limit is reached, additional toasts are queued until the user dismisses one. Each <Toast> is a non-modal ARIA alertdialog, containing the content of the notification and a close button.

Landmark regions including the toast container can be navigated using the keyboard by pressing the F6 key to move forward, and the Shift + F6 key to move backward. This provides an easy way for keyboard users to jump to the toasts from anywhere in the app. When the last toast is closed, keyboard focus is restored.

import {Button, Text, Toast, ToastContent, ToastRegion} from 'react-aria-components';

<ToastRegion>
  {({ toast }) => (
    <Toast toast={toast}>
      <ToastContent>
        <Text slot="title" />
        <Text slot="description" />
      </ToastContent>
      <Button slot="close" />
    </Toast>
  )}
</ToastRegion>
import {
  Button,
  Text,
  Toast,
  ToastContent,
  ToastRegion
} from 'react-aria-components';

<ToastRegion>
  {({ toast }) => (
    <Toast toast={toast}>
      <ToastContent>
        <Text slot="title" />
        <Text slot="description" />
      </ToastContent>
      <Button slot="close" />
    </Toast>
  )}
</ToastRegion>
import {
  Button,
  Text,
  Toast,
  ToastContent,
  ToastRegion
} from 'react-aria-components';

<ToastRegion>
  {({ toast }) => (
    <Toast
      toast={toast}
    >
      <ToastContent>
        <Text slot="title" />
        <Text slot="description" />
      </ToastContent>
      <Button slot="close" />
    </Toast>
  )}
</ToastRegion>

Auto-dismiss#


Toasts support a timeout option to automatically hide them after a certain amount of time. For accessibility, toasts should have a minimum timeout of 5 seconds to give users enough time to read them. If a toast includes action buttons or other interactive elements it should not auto dismiss. In addition, timers will automatically pause when the user focuses or hovers over a toast.

Be sure only to automatically dismiss toasts when the information is not important, or may be found elsewhere. Some users may require additional time to read a toast message, and screen zoom users may miss toasts entirely.

<Button
  onPress={() => queue.add({title: 'Toast is done!'}, {timeout: 5000})}>
  Show toast
</Button>
<Button
  onPress={() =>
    queue.add({ title: 'Toast is done!' }, {
      timeout: 5000
    })}>
  Show toast
</Button>
<Button
  onPress={() =>
    queue.add({
      title:
        'Toast is done!'
    }, {
      timeout: 5000
    })}>
  Show toast
</Button>

Programmatic dismissal#


Toasts may be programmatically dismissed if they become irrelevant before the user manually closes them. queue.add returns a key for the toast which may be passed to queue.close to dismiss the toast.

function Example() {
  let [toastKey, setToastKey] = React.useState(null);

  return (
    <Button
      onPress={() => {
        if (!toastKey) {
          setToastKey(queue.add({ title: 'Unable to save' }, {
            onClose: () => setToastKey(null)
          }));        } else {
          queue.close(toastKey);        }
      }}
    >
      {toastKey ? 'Hide' : 'Show'} Toast
    </Button>
  );
}
function Example() {
  let [toastKey, setToastKey] = React.useState(null);

  return (
    <Button
      onPress={() => {
        if (!toastKey) {
          setToastKey(
            queue.add({ title: 'Unable to save' }, {
              onClose: () => setToastKey(null)
            })
          );        } else {
          queue.close(toastKey);        }
      }}
    >
      {toastKey ? 'Hide' : 'Show'} Toast
    </Button>
  );
}
function Example() {
  let [
    toastKey,
    setToastKey
  ] = React.useState(
    null
  );

  return (
    <Button
      onPress={() => {
        if (!toastKey) {
          setToastKey(
            queue.add({
              title:
                'Unable to save'
            }, {
              onClose:
                () =>
                  setToastKey(
                    null
                  )
            })
          );        } else {
          queue.close(
            toastKey
          );        }
      }}
    >
      {toastKey
        ? 'Hide'
        : 'Show'} Toast
    </Button>
  );
}

Animations#


Toast entry and exit animations can be done using third party animation libraries like Motion, or using native CSS view transitions.

This example shows how to use the wrapUpdate option of ToastQueue to wrap state updates in a CSS view transition. The toast.key can be used to assign a viewTransitionName to each Toast.

import {flushSync} from 'react-dom';

const queue = new ToastQueue<MyToastContent>({
  // Wrap state updates in a CSS view transition.
  wrapUpdate(fn) {
    if ('startViewTransition' in document) {
      document.startViewTransition(() => {
        flushSync(fn);
      });
    } else {
      fn();
    }
  }});

<ToastRegion queue={queue}>
  {({toast}) => (
    <Toast
      style={{viewTransitionName: toast.key}}      toast={toast}>
      <ToastContent>
        <Text slot="title">{toast.content.title}</Text>
        <Text slot="description">{toast.content.description}</Text>
      </ToastContent>
      <Button slot="close">x</Button>
    </Toast>
  )}
</ToastRegion>
<Button onPress={() => queue.add({title: 'Toasted!'})}>Toast</Button>
import {flushSync} from 'react-dom';

const queue = new ToastQueue<MyToastContent>({
  // Wrap state updates in a CSS view transition.
  wrapUpdate(fn) {
    if ('startViewTransition' in document) {
      document.startViewTransition(() => {
        flushSync(fn);
      });
    } else {
      fn();
    }
  }});

<ToastRegion queue={queue}>
  {({ toast }) => (
    <Toast
      style={{ viewTransitionName: toast.key }}      toast={toast}
    >
      <ToastContent>
        <Text slot="title">{toast.content.title}</Text>
        <Text slot="description">
          {toast.content.description}
        </Text>
      </ToastContent>
      <Button slot="close">x</Button>
    </Toast>
  )}
</ToastRegion>
<Button onPress={() => queue.add({ title: 'Toasted!' })}>
  Toast
</Button>
import {flushSync} from 'react-dom';

const queue =
  new ToastQueue<
    MyToastContent
  >({
    // Wrap state updates in a CSS view transition.
    wrapUpdate(fn) {
      if (
        'startViewTransition' in
          document
      ) {
        document
          .startViewTransition(
            () => {
              flushSync(
                fn
              );
            }
          );
      } else {
        fn();
      }
    }  });

<ToastRegion
  queue={queue}
>
  {({ toast }) => (
    <Toast
      style={{
        viewTransitionName:
          toast.key
      }}      toast={toast}
    >
      <ToastContent>
        <Text slot="title">
          {toast
            .content
            .title}
        </Text>
        <Text slot="description">
          {toast
            .content
            .description}
        </Text>
      </ToastContent>
      <Button slot="close">
        x
      </Button>
    </Toast>
  )}
</ToastRegion>
<Button
  onPress={() =>
    queue.add({
      title: 'Toasted!'
    })}
>
  Toast
</Button>
Show CSS
.react-aria-Toast {
  view-transition-class: toast;
}

::view-transition-new(.toast):only-child {
  animation: slide-in 400ms;
}

::view-transition-old(.toast):only-child {
  animation: slide-out 400ms;
}

@keyframes slide-out {
  to {
    translate: 100% 0;
    opacity: 0;
  }
}

@keyframes slide-in {
  from {
    translate: 100% 0;
    opacity: 0;
  }
}
.react-aria-Toast {
  view-transition-class: toast;
}

::view-transition-new(.toast):only-child {
  animation: slide-in 400ms;
}

::view-transition-old(.toast):only-child {
  animation: slide-out 400ms;
}

@keyframes slide-out {
  to {
    translate: 100% 0;
    opacity: 0;
  }
}

@keyframes slide-in {
  from {
    translate: 100% 0;
    opacity: 0;
  }
}
.react-aria-Toast {
  view-transition-class: toast;
}

::view-transition-new(.toast):only-child {
  animation: slide-in 400ms;
}

::view-transition-old(.toast):only-child {
  animation: slide-out 400ms;
}

@keyframes slide-out {
  to {
    translate: 100% 0;
    opacity: 0;
  }
}

@keyframes slide-in {
  from {
    translate: 100% 0;
    opacity: 0;
  }
}

Props#


ToastRegion#

<ToastRegion> renders a group of toasts.

NameTypeDescription
queueToastQueue<T>The queue of toasts to display.
children( (renderProps: {
toast: QueuedToast<T>
} )) => ReactElement
A function to render each toast.
classNamestring( (values: ToastRegionRenderProps<T>{
defaultClassName: stringundefined
} )) => string
The CSS className for the element. A function may be provided to compute the class based on component state.
styleCSSProperties( (values: ToastRegionRenderProps<T>{
defaultStyle: CSSProperties
} )) => CSSPropertiesundefined
The inline style for the element. A function may be provided to compute the style based on component state.
Accessibility
NameTypeDefaultDescription
aria-labelstring"Notifications"An accessibility label for the toast region.
aria-labelledbystringIdentifies the element (or elements) that labels the current element.
aria-describedbystringIdentifies the element (or elements) that describes the object.
aria-detailsstringIdentifies the element (or elements) that provide a detailed, extended description for the object.

Toast#

<Toast> renders an individual toast.

NameTypeDescription
toastQueuedToast<T>The toast object.
childrenReactNode( (values: ToastRenderProps<T>{
defaultChildren: ReactNodeundefined
} )) => ReactNode
The children of the component. A function may be provided to alter the children based on component state.
classNamestring( (values: ToastRenderProps<T>{
defaultClassName: stringundefined
} )) => string
The CSS className for the element. A function may be provided to compute the class based on component state.
styleCSSProperties( (values: ToastRenderProps<T>{
defaultStyle: CSSProperties
} )) => CSSPropertiesundefined
The inline style for the element. A function may be provided to compute the style based on component state.
Accessibility
NameTypeDescription
aria-labelstringDefines a string value that labels the current element.
aria-labelledbystringIdentifies the element (or elements) that labels the current element.
aria-describedbystringIdentifies the element (or elements) that describes the object.
aria-detailsstringIdentifies the element (or elements) that provide a detailed, extended description for the object.

ToastContent#

<ToastContent> renders the main content of a toast, including the title and description. It accepts all HTML attributes.

ToastQueue API#


A ToastQueue manages the state for a <ToastRegion>. The state is stored outside React so that you can trigger toasts from anywhere in your application, not just inside components.

Properties

NameTypeDescription
visibleToastsQueuedToast<T>[]The currently visible toasts.

Methods

MethodDescription
constructor( (options?: ToastStateProps )): void
subscribe( (fn: () => void )): voidSubscribes to updates to the visible toasts.
add( (content: T, , options: ToastOptions )): voidAdds a new toast to the queue.
close( (key: string )): voidCloses a toast.
pauseAll(): voidPauses the timers for all visible toasts.
resumeAll(): voidResumes the timers for all visible toasts.