artui
Components

Toast

View source

Accessible toast notifications using WAI-ARIA live regions and the native Popover API. info / success / warning / error severity, pause-on-hover, no focus theft, compile-time enforcement of accessible titles.

npx artui@latest add toast

Playground

Toasts render as neutral cards with a colored left accent border per type, and auto-dismiss after 5s. Press Alt+T to move focus into the newest toast, then Esc to dismiss it.

variant
duration
action
description
maxVisible
3

Usage

Shortcut helpers

import { useToast } from '@/components/toast';

function SaveButton() {
  const toast = useToast();
  return (
    <button onClick={() => toast.success('Changes saved.')}>
      Save
    </button>
  );
}

Full options via show()

const toast = useToast();

toast.show({
  title: 'Profile updated.',
  description: 'Your display name is now visible to collaborators.',
  type: 'success',
  duration: 8000,
});

Persistent error with an action

Error toasts with a short duration and no action fire a dev warning. Pass duration: null and provide an action so the user can respond.

toast.error('Upload failed. The file may be corrupted.', {
  duration: null,
  action: {
    label: 'Retry',
    onAction: retryUpload,
  },
});

Awaiting dismissal

ToastHandle.done resolves when the toast is dismissed, expired, collapsed, or the provider unmounts.

const handle = toast.warning('Saving draft…', { duration: null });
await saveDraft();
handle.dismiss();
await handle.done;
showNextStep();

API

ToastProvider

Mount exactly one ToastProvider at the root of your application. Mounting more than one duplicates the live-region DOM nodes; a dev overlay fires if duplicates are detected.

PropTypeDefaultDescription
childrenrequiredReactNodenoneApplication content. Mount exactly one ToastProvider at the root of your app.
maxVisiblenumber3Maximum number of toasts visible at once per region. Oldest is collapsed when the limit is exceeded.
defaultDurationnumber | null5000Default auto-dismiss duration in milliseconds. Pass null for persistent toasts that require explicit dismissal.

ToastOptions

Passed to toast.show(). The shortcut helpers (toast.success(), toast.error(), …) accept the same fields minus title and type.

PropTypeDefaultDescription
titlerequiredAccessibleTextnoneRequired accessible title for the notification. Empty strings and placeholder values are rejected at compile time.
descriptionstringnoneOptional secondary body text shown below the title.
typeToastType"info"Notification severity. info and success are polite; warning and error are assertive.
durationnumber | nullnonePer-toast duration override. null makes the toast persistent. Action toasts have a 10 s minimum.
actionToastActionnoneOptional action button. Rendered as a real <button>; invoking it dismisses the toast.

ToastAction

PropTypeDefaultDescription
labelrequiredAccessibleTextnoneVisible label for the action button. Placeholder values are compile errors.
onActionrequired() => voidnoneCalled when the action button is activated.

ToastHandle

Returned by toast.show() and all shortcut helpers.

PropTypeDefaultDescription
idstringnoneUnique identifier for this toast.
dismiss() => voidnoneProgrammatically dismiss the toast.
donePromise<void>noneResolves when the toast leaves the DOM for any reason.

useToast()

Returns a ToastApi. Must be called inside a ToastProvider.

PropTypeDefaultDescription
show(opts: ToastOptions) => ToastHandlenoneShow a toast with full options.
success(title: AccessibleText, opts?: ToastShortcutOptions) => ToastHandlenoneShortcut for type: "success".
error(title: AccessibleText, opts?: ToastShortcutOptions) => ToastHandlenoneShortcut for type: "error".
warning(title: AccessibleText, opts?: ToastShortcutOptions) => ToastHandlenoneShortcut for type: "warning".
info(title: AccessibleText, opts?: ToastShortcutOptions) => ToastHandlenoneShortcut for type: "info".

Keyboard

KeyAction
Alt+TMove focus into the newest visible toast (action → close → toast)
EscapeDismiss the focused toast and return focus to the element that was active when it was shown
TabMove focus between the action and close buttons within a toast
F6Cycle through page landmarks (browser native; reaches the toast region as a landmark)
EnterActivate the focused action or close button
SpaceActivate the focused action or close button

Focus is never moved to a toast on render. The timer pauses while a toast has keyboard focus and resumes when focus leaves.

Accessibility

CriterionNameHow the component satisfies it
4.1.3(AA)Status MessagesInfo and success toasts are rendered in an aria-live=polite region (role=status). Warning and error toasts are rendered in an aria-live=assertive region (role=alert). Both regions are mounted as persistent DOM nodes so screen readers maintain their subscription even after all toasts dismiss.
2.2.1(A)Timing AdjustableTimers pause on hover, keyboard focus, document hidden, and prefers-reduced-motion. The 10 s minimum is enforced for action toasts. A dev warning fires for error toasts with short durations.
2.1.1(A)KeyboardEsc dismisses the focused toast. Alt+T moves focus to the most-recent toast's action or close button. Action and close are real <button> elements activatable by keyboard.
2.4.3(A)Focus OrderToasts do not steal focus on render. Focus returns to the element that was focused when show() was called, when the toast is dismissed via close/action/Esc: unless the user has already moved focus elsewhere.
1.4.1(A)Use of ColorType (info/success/warning/error) is conveyed via a visually-hidden text prefix on the title, not color alone.
2.4.7(AA)Focus VisibleFocus indicators on action and close buttons use outline (not box-shadow) to remain visible in Windows High Contrast Mode.

Do

Mount exactly one ToastProvider at your app root

// app/layout.tsx
export default function RootLayout({ children }: { children: ReactNode }) {
  return (
    <html lang="en">
      <body>
        <ToastProvider>{children}</ToastProvider>
      </body>
    </html>
  );
}

Use duration: null for errors and any toast with an action

toast.error('Payment declined. Please check your card details.', {
  duration: null,
  action: { label: 'Update card', onAction: openBillingSettings },
});

Use shortcut helpers for common severity levels

toast.success('Invitation sent.');
toast.warning('Draft autosaved. Manual save recommended before closing.');

Await handle.done when you need to chain work after dismissal

const handle = toast.info('Processing…', { duration: null });
await processData();
handle.dismiss();

Don't

Mount multiple ToastProvider instances

<ToastProvider>
  <Header />
  <ToastProvider>   {/* dev overlay fires */}
    <MainContent />
  </ToastProvider>
</ToastProvider>

A dev overlay fires and screen readers announce every notification twice.

Pass an empty or placeholder title

toast.show({ title: '' });          // compile error
toast.show({ title: 'title' });     // compile error
toast.show({ title: 'TODO' });      // compile error

AccessibleText rejects these at compile time.

Use error toasts with short auto-dismiss and no action

toast.error('Sync failed.', { duration: 3000 });
// Dev warning: error with duration 3000ms and no action.

Pass duration: null or add an action so users can recover.

Call useToast() outside ToastProvider

function Widget() {
  const toast = useToast();  // throws if no provider in the tree
}

On this page