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.
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 toastToast 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.
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
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:
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.
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.
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.
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 }} />.
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.
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.
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