Toast

Transient notification queue with swipe to dismiss.

A transient notification, with a queue and swipe to dismiss.

The queue lives outside React, so toast.show() works from anywhere — API clients, event handlers, code with no component around it. PanelUIProvider mounts the viewport for you.

Use it for something that happened and needs no response. For a condition that persists on the screen, use Alert.

Installation

Toast ships with the library — no separate install.

import { Toast, Button, useToast } from 'panelui-native';

Or copy the source into your project, to own and edit it:

npx panelui-cli@latest add toast

Usage

const { toast } = useToast();

// A bare string
toast.show('Link copied');

// A config object
toast.show({
  variant: 'success',
  label: 'Deployment complete',
  description: 'panelui.dev is live on production.',
  actionLabel: 'View',
  onActionPress: ({ hide }) => hide(),
});

// A custom component
toast.show({
  component: ({ hide }) => (
    <Toast variant="info" onHide={hide}>
      <Toast.Indicator />
      <Toast.Content>
        <Toast.Title>Custom</Toast.Title>
      </Toast.Content>
      <Toast.Close />
    </Toast>
  ),
});

Composition

<Toast>
  <Toast.Indicator />
  <Toast.Content>
    <Toast.Title>…</Toast.Title>
    <Toast.Description>…</Toast.Description>
  </Toast.Content>
  <Toast.Action>…</Toast.Action>
  <Toast.Close />
</Toast>
  • Toast.Indicator — Status icon, picked from the variant.
  • Toast.Content — Flex-1 wrapper for title and description.
  • Toast.Title — Heading, coloured by the variant.
  • Toast.Description — Body text.
  • Toast.Action — Trailing action button. Dismisses the toast after its own onPress.
  • Toast.Close — Icon-only dismiss button.

Examples

Firing one

Toasts are queued through useToast, so any component can raise one without rendering it.

const { toast } = useToast();

<Button
  onPress={() =>
    toast({
      variant: 'success',
      title: 'Saved',
      description: 'Your changes are live.',
    })
  }
>
  Save
</Button>

With an action

toast({
  variant: 'default',
  title: 'Message archived',
  action: { label: 'Undo', onPress: restore },
});

An error you have to dismiss

toast({
  variant: 'destructive',
  title: 'Upload failed',
  description: 'The file is larger than 25 MB.',
  duration: 0, // stays until dismissed
});

Versions

Simple string

A string on its own. No title, no description, no action — the whole toast is the sentence, which is what a copy-to-clipboard or a saved-draft actually has to say.

toast.show('Link copied to clipboard');

With action

A title, a description and one thing to do about the result. actionLabel draws the control; the toast stays up long enough to reach it.

toast.show({
  variant: 'success',
  label: 'Deployment complete',
  description: 'panelui.dev is live on production.',
  actionLabel: 'View',
});

Destructive, top

A failure raised at the top edge. placement is per-toast rather than per-app, because the edge a message belongs at follows from the message: a result of something you just did belongs near your thumb, and a failure you did not ask for does not.

toast.show({
  variant: 'destructive',
  label: 'Upload failed',
  description: 'The file exceeds the 25 MB limit.',
  placement: 'top',
});

Stack four

Four arriving in turn. Three are visible at once — the newest in front, the rest peeking out behind it — and the deck collapses as each is dismissed.

They are fired a fifth of a second apart rather than together, because the deck is built by things arriving: four on one frame is a stack that was already there.

(['default', 'info', 'success', 'warning'] as const).forEach((variant, index) =>
  setTimeout(
    () =>
      toast.show({
        variant,
        label: `Notification ${index + 1}`,
        description: 'Swipe down to dismiss the front one.',
        duration: 8000,
      }),
    index * 220
  )
);

Hide all

Clearing the whole queue at once, whatever is in it. Use it when the thing the toasts were about has gone — a screen the user has left, a job they cancelled.

toast.hideAll();

Custom component

The toast drawn entirely by the caller. The deck still owns its timing, its position and its swipe; component only replaces what is inside. hide is handed in so a control of your own can dismiss it.

toast.show({
  duration: 6000,
  component: ({ hide }) => (
    <Toast variant="info" onHide={hide}>
      <Toast.Indicator />
      <Toast.Content>
        <Toast.Title>Custom component</Toast.Title>
        <Toast.Description>Rendered entirely by the caller.</Toast.Description>
      </Toast.Content>
      <Toast.Close />
    </Toast>
  ),
});

Lifecycle contract

CaseContract
default initializationA toast begins with its complete visible-time duration.
controlled acceptanceNot applicable: the toast store owns entries rather than accepting a value prop.
controlled rejectionNot applicable: dismiss requests mutate the owning store directly.
external resetClearing or replacing the viewport does not spend background time.
disabled pathInactive AppState and an unmounted viewport pause every countdown.
prop replacementDuration is captured per toast; viewport lifecycle changes preserve remaining time.
unmount cleanupViewport release pauses timers and removes AppState ownership.
reduced motionNot applicable to countdown ownership; visual transitions own motion separately.
callback countsEach timer fires dismissal once after its complete foreground-visible duration.

Executable evidence: packages/panelui/test/toast-timer-lifecycle.test.mjs (3 tests).

Variants

variant

  • default (default)
  • info
  • success
  • warning
  • destructive
toast({ variant: 'default', title: 'Saved' });
toast({ variant: 'info', title: 'Syncing' });
toast({ variant: 'success', title: 'Deployed' });
toast({ variant: 'warning', title: 'Almost out of space' });
toast({ variant: 'destructive', title: 'Upload failed' });

API Reference

Toast

PropTypeDefaultDescription
classNamestring
onHide() => voidCalled when the close button is pressed or the toast is swiped away.

Toast.Indicator

PropTypeDefaultDescription
classNamestring
iconProps{ size?: number; color?: string }

Toast.Close

PropTypeDefaultDescription
classNamestring

Every part also accepts the underlying React Native props (ViewProps or TextProps) and a className for Tailwind utilities.

Notes

Toasts stack as an overlapping deck: the newest is fully visible and the ones behind peek out, with anything past the third fading rather than accumulating. Swiping toward the edge the toast entered from dismisses it; dragging the other way rubber-bands.

Auto-dismiss durations count visible foreground time: countdowns pause while the app is inactive or in the background, then resume where they left off. toast.hide(id) dismisses one, toast.hideAll() clears the queue. A duration of 0 keeps a toast up until it is dismissed.

Public exports

Values: Toast, ToastViewport, toast, useToast

Types: ToastProps, ToastOptions, ToastItem, ToastVariant, ToastPlacement, ToastHandle, ToastIndicatorProps, ToastCloseProps

On this page