Back to React Spectrum

Toast

packages/react-aria-components/docs/Toast.mdx

2022-12-1610.6 KB
Original Source

{/* Copyright 2025 Adobe. All rights reserved. This file is licensed to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */}

import {Layout} from '@react-spectrum/docs'; export default Layout;

import docs from 'docs:react-aria-components'; import {PropTable, HeaderInfo, TypeLink, PageDescription, StateTable, ContextTable, ClassAPI} from '@react-spectrum/docs'; import styles from '@react-spectrum/docs/src/docs.css'; import packageData from 'react-aria-components/package.json'; import ChevronRight from '@spectrum-icons/workflow/ChevronRight'; import {ExampleCard} from '@react-spectrum/docs/src/ExampleCard'; import {StarterKits} from '@react-spectrum/docs/src/StarterKits'; import Anatomy from '/packages/react-aria/docs/toast/toast-anatomy.svg'; import {Keyboard} from '@react-spectrum/text'; import {InlineAlert, Content, Heading} from '@adobe/react-spectrum';


category: Status keywords: [toast, notifications, alert, aria] type: component preRelease: alpha

Toast

<PageDescription>{docs.exports.UNSTABLE_Toast.description}</PageDescription>

<HeaderInfo packageData={packageData} componentNames={['ToastRegion', 'Toast']} sourceData={[ {type: 'W3C', url: 'https://www.w3.org/WAI/ARIA/apg/patterns/alertdialog/'} ]} />

<InlineAlert variant="notice" marginTop={60}> <Heading>Under construction</Heading> <Content>This component is in <strong>alpha</strong>. More documentation is coming soon!</Content> </InlineAlert>

Example

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

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

// 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 size={16} /></Button>
          </Toast>
        )}
      </ToastRegion>
    </>
  );
}

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

tsx
<Button
  onPress={() => queue.add({
    title: 'Toast complete!',
    description: 'Great success.'
  })}>
  Toast
</Button>
<details> <summary style={{fontWeight: 'bold'}}><ChevronRight size="S" /> Show CSS</summary>
css
@import './Button.mdx' layer(button);
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);
    }
  }
}
</details>

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

<Anatomy role="img" aria-label="Toast anatomy diagram, showing the toast's title and close button within the toast region." />

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. The toast's content gets announced by screen readers when the toast appears, so it is recommended to render the close button as a sibling of <ToastContent> instead of inside it, but this is not a requirement.

Landmark regions including the toast container can be navigated using the keyboard by pressing the <Keyboard>F6</Keyboard> key to move forward, and the <Keyboard>Shift</Keyboard> + <Keyboard>F6</Keyboard> 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.

tsx
import {ToastRegion, Toast, ToastContent, Text, Button} 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.

tsx
<Button
  /*- begin highlight -*/
  onPress={() => queue.add({title: 'Toast is done!'}, {timeout: 5000})}
  /*- end highlight -*/
>
  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.

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

  return (
    <Button
      onPress={() => {
        if (!toastKey) {
          ///- begin highlight -///
          setToastKey(queue.add({title: 'Unable to save'}, {onClose: () => setToastKey(null)}));
          ///- end highlight -///
        } else {
          ///- begin highlight -///
          queue.close(toastKey);
          ///- end highlight -///
        }
      }}>
      {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.

tsx
import {flushSync} from 'react-dom';

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

<ToastRegion queue={queue}>
  {({toast}) => (
    <Toast
      /*- begin highlight -*/
      style={{viewTransitionName: toast.key}}
      /*- end highlight -*/
      toast={toast}>
      <ToastContent>
        <Text slot="title">{toast.content.title}</Text>
        <Text slot="description">{toast.content.description}</Text>
      </ToastContent>
      <Button slot="close"><X size={16} /></Button>
    </Toast>
  )}
</ToastRegion>
<Button onPress={() => queue.add({title: 'Toasted!'})}>Toast</Button>
<details> <summary style={{fontWeight: 'bold'}}><ChevronRight size="S" /> Show CSS</summary>
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;
  }
}
</details>

Props

ToastRegion

<ToastRegion> renders a group of toasts.

<PropTable component={docs.exports.UNSTABLE_ToastRegion} links={docs.links} />

Toast

<Toast> renders an individual toast.

<PropTable component={docs.exports.UNSTABLE_Toast} links={docs.links} />

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.

<ClassAPI links={docs.links} class={docs.exports.UNSTABLE_ToastQueue} />