Toast

Stacked notifications driven by an imperative manager (add, update, close, promise): limit, timers that pause on hover/focus, keyboard access via F6, swipe to dismiss.

Installation

npx nuecrea-ui add toast

Usage

'use client';

import { Button } from '@/components/ui/button';
import * as Toast from '@/components/ui/toast';

function Toasts() {
  const { toasts } = Toast.useToastManager();
  return toasts.map((t) => (
    <Toast.Root key={t.id} toast={t}>
      <Toast.Content>
        <div className="flex min-w-0 flex-1 flex-col gap-1">
          <Toast.Title />
          <Toast.Description />
        </div>
        {t.actionProps ? <Toast.Action /> : null}
        <Toast.Close>Dismiss</Toast.Close>
      </Toast.Content>
    </Toast.Root>
  ));
}

function Buttons() {
  const manager = Toast.useToastManager();
  return (
    <div className="flex flex-wrap gap-2">
      <Button
        variant="outline"
        onClick={() => manager.add({ title: 'Saved', description: 'Your changes were saved.' })}
      >
        Show toast
      </Button>
      <Button
        variant="outline"
        onClick={() =>
          manager.add({
            title: 'Upload failed',
            description: 'The file is larger than 10 MB.',
            type: 'error',
            priority: 'high',
          })
        }
      >
        Show error
      </Button>
      <Button
        variant="outline"
        onClick={() =>
          manager.add({
            title: 'Message archived',
            description: 'It was moved to Archive.',
            type: 'info',
            actionProps: { children: 'Undo', onClick: () => manager.add({ title: 'Restored' }) },
          })
        }
      >
        With action
      </Button>
      <Button
        variant="outline"
        onClick={() =>
          manager.add({ title: 'Quick note', description: 'Gone in a second.', timeout: 1000 })
        }
      >
        Short timeout
      </Button>
      <Button
        variant="outline"
        onClick={() =>
          manager.promise(new Promise((resolve) => setTimeout(resolve, 800)), {
            loading: { title: 'Uploading…', description: 'Please wait.' },
            success: { title: 'Uploaded', description: 'report.pdf is ready.' },
            error: { title: 'Upload failed' },
          })
        }
      >
        Promise
      </Button>
    </div>
  );
}

export default function ToastStory() {
  return (
    <Toast.Provider timeout={60000}>
      <main>
        <Buttons />
      </main>
      <Toast.Portal>
        <Toast.Viewport>
          <Toasts />
        </Toast.Viewport>
      </Toast.Portal>
    </Toast.Provider>
  );
}

API reference

Manager: React: Toast.useToastManager() / Toast.createToastManager(). Angular: inject(ToastManager) (provided by [libToastProvider] or app-wide). Same methods: toasts, add(options), update(id, options), close(id?), promise(promise, { loading, success, error }).

Toast.Provider

Provides the manager; configures limit and default timeout.

PropTypeDefaultDescription
limitnumber3Toasts shown at once; older ones get data-limited.
timeoutnumber5000Default auto-dismiss in ms (0 = never).

Toast.Portal

Renders the viewport at the end of <body>. Renders <div>.

Toast.Viewport

The notifications region (polite live region). Hover/focus expands the stack and pauses timers; F6 focuses it. Renders <div> with role region.

Data attributePresent when
data-expandedhovered or keyboard-focused
CSS variableDescription
--toast-frontmost-heightHeight of the frontmost toast.

Toast.Root

One toast (role=dialog, alertdialog for priority high). Swipe to dismiss; Escape closes when focused. Renders <div> with role dialog.

PropTypeDefaultDescription
toastToastObject—Required: the toast to render.
swipeDirectionDirection | Direction[]"['down', 'right']"Directions that dismiss.
Data attributePresent when
data-expandedthe stack is expanded
data-limitedover the limit (hidden, inert)
data-swipe-directionbeing/been swiped that way
data-swipingbeing dragged
data-typethe toast has a type
data-starting-styleanimating in
data-ending-styleanimating out
CSS variableDescription
--toast-indexPosition in the stack
--toast-offset-yDistance from the front when expanded
--toast-heightNatural height
--toast-swipe-movement-xDrag offset
--toast-swipe-movement-yDrag offset

Toast.Content

The toast's content. Renders <div>.

Data attributePresent when
data-behindnot the frontmost toast
data-expandedthe stack is expanded

Toast.Title

Renders the toast's title (or children). Renders <h2>.

Data attributePresent when
data-typethe toast has a type

Toast.Description

Renders the toast's description (or children). Renders <p>.

Data attributePresent when
data-typethe toast has a type

Toast.Action

Runs actionProps.onClick; label from actionProps.children. Renders <button>.

Data attributePresent when
data-typethe toast has a type

Toast.Close

Dismisses the toast. Renders <button>.

Data attributePresent when
data-typethe toast has a type

Keyboard

KeyAction
F6Focus the toast region (from anywhere).
TabMove through toasts and their buttons.
EscapeClose the focused toast; focus moves to the next one.
Shift+TabFrom the region: return to where focus was.