Toast

A brief message about something that just happened — shown from anywhere, stacked at the edge of the screen, swiped away or gone on its own.

Open Source

Installation

Toast ships with the library — install it once, then import:

import { Toast } from "@delacour/react-native-ui/toast";

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

bunx delacour@latest add toast

Toast draws through the overlay layer, so mount OverlayProvider once at the app root — and mount ToastViewport once inside it, beside the navigator. The viewport is what draws every toast; nothing appears without it.

app/_layout.tsx
import { BottomSheetProvider } from "@/components/ui/bottom-sheet";
import { OverlayProvider } from "@/components/ui/overlay";
import { DelacourProvider } from "@/components/ui/provider";
import { ToastViewport } from "@/components/ui/toast";
import { Stack } from "expo-router";

export default function RootLayout() {
  return (
    <DelacourProvider>
      <OverlayProvider>
        <BottomSheetProvider>
          <Stack />
          <ToastViewport />
        </BottomSheetProvider>
      </OverlayProvider>
    </DelacourProvider>
  );
}

Usage

app/index.tsx
import { Button } from "@/components/ui/button";
import { toast } from "@/components/ui/toast";
import * as Clipboard from "expo-clipboard";
import { View } from "react-native";

export default function Example() {
  return (
    <View className="flex-1 items-center justify-center p-4">
      <Button
        onPress={async () => {
          await Clipboard.setStringAsync("https://ui.delacour.co.nz");
          toast("Link copied");
        }}
      >
        Copy link
      </Button>
    </View>
  );
}

Outside React

toast is a plain function over a module-level store, not a hook. Call it from an API client, a mutation's onError, a background task — anywhere:

lib/api.ts
import { toast } from "@/components/ui/toast";

export async function saveDraft(draft: Draft): Promise<void> {
  const response = await fetch("/api/drafts", { method: "POST", body: JSON.stringify(draft) });
  if (!response.ok) toast.error("Could not save the draft", { description: "Your changes are still here." });
}

Statuses

The five statuses are Alert's, with the same glyphs and title colours, so a toast and an alert saying the same thing look like one family. Success, warning and destructive also play their notification haptic.

Statuses
toast("Link copied");
toast.info("Sync paused");
toast.success("Changes saved", { description: "Everyone on the project sees them now." });
toast.warning("Storage almost full");
toast.error("Upload failed"); // status "destructive"

Action

One action — an undo, a retry. Pressing it runs onPress and hides the toast. A toast with an action stays six seconds rather than four.

With an action
toast.show({
  title: "Conversation archived",
  action: { label: "Undo", onPress: () => restore(conversation) },
});

Promise

toast.promise shows one toast with a spinner and turns it into the success or the failure in place. It returns the promise it was given, so await still sees a rejection.

Promise
await toast.promise(uploadPhotos(), {
  loading: "Uploading…",
  success: (count) => `${count} photos uploaded`,
  error: "Upload failed",
});

Placement and stacking

placement is "bottom" by default, or "top". Bottom toasts sit above the home indicator and ride up with the keyboard; top toasts sit under the status bar. ToastViewport's offset adds space at either edge — to clear a tab bar, <ToastViewport offset={{ bottom: 56 }} />.

Placement

Each edge draws its three newest toasts, the newest in front and each older one a step further back. The rest wait their turn and appear as the ones in front leave. Several shown at once enter a beat apart.

Stacking

Hiding

A toast hides itself after duration ms: 4000, or 6000 with an action. duration: 0 keeps it until it is hidden. Its clock pauses while the app is in the background and while a finger is on it.

Persistent

Swipe it toward its edge, or sideways, to dismiss it; pulled toward the middle it resists and springs back. The ✕, toast.hide(id) and toast.hideAll() hide it too, and onHide runs once it has gone.

const { id } = toast.warning("You are offline", { duration: 0 });
// later
toast.hide(id);

Pass an id to replace a toast in place rather than adding another — toast.update(id, patch) merges into it.

Custom

render draws the whole card. Compose Toast to keep the look; inside the viewport it hides the toast it is drawn in on its own.

toast.show({
  placement: "top",
  render: () => (
    <Toast status="info">
      <Toast.Indicator>
        <Icon icon={IconPeople} />
      </Toast.Indicator>
      <Toast.Content>
        <Toast.Title>Priya joined the project</Toast.Title>
        <Toast.Description>She can edit every file.</Toast.Description>
      </Toast.Content>
      <Toast.Close />
    </Toast>
  ),
});

Anatomy

<Toast>
  <Toast.Indicator />
  <Toast.Content>
    <Toast.Title />
    <Toast.Description />
  </Toast.Content>
  <Toast.Action />
  <Toast.Close />
</Toast>

Accessibility

Every toast is announced as it appears — its title, then its description — and again when it is updated, so a promise toast is read out loading and then done. A warning or a failure interrupts what is being read. With VoiceOver or TalkBack on, a toast stays at least ten seconds, long enough to be read in full.

A toast never takes focus. It is one element: its action is the element's activate action, and dismissing it is the escape gesture or the "Dismiss" action. A failure is announced as an alert. Under Reduce Motion it fades in without sliding. Android's back button is left to whatever is under the toasts.

API Reference

toast

Prop

Type

ToastOptions

Prop

Type

A custom toast takes render: (handle: ToastHandle) => ReactNode in place of title, description, status and action.

ToastViewport

Prop

Type

Toast

Takes React Native's ViewProps.

Prop

Type

Toast.Action and Toast.Close take Pressable's props; Toast.Title and Toast.Description take Text's. Toast.Indicator's children replace the status glyph.

useToast

Prop

Type

On this page