If you’re using the unstyled primitives directly, it’s also published on its own:
yarn
npm
bun
pnpm
yarn add @tamagui/toast
Native OS toasts are opt-in. By default toasts render as in-app Tamagui views on
every platform, so adding Toast to your app never pulls in a native dependency.
See Native toasts if you want the real iOS/Android system
toast.
Quick start
Toast is a composable component API: you mount the parts, and toast() drives
them from anywhere in your app.
// copy-paste: these skins import from files you own. copy each registry item
// below into your app, then adjust the relative import paths to fit.
Mount this inside your TamaguiProvider, usually next to your app navigation or
root layout. toast() is global, so callers don’t need to be inside <Toast>.
<Toast.List /> renders the default styled toast. Pass renderItem to own the
markup completely:
<Toast.ListrenderItem={({ toast: t, index })=>(<Toast.Itemkey={t.id}toast={t}index={index}><Toast.Title>{t.title}</Toast.Title><Toast.Description>{t.description}</Toast.Description><Toast.Close/></Toast.Item>)}/>
Omit renderItem and Toast.List renders the default styled toast content.
Anatomy
<Toast>{/* Root - context and position */}
<Toast.Viewport>{/* Container with portal */}
<Toast.Item>{/* Individual toast with stacking/drag */}
Hook to access toast state from a descendant of <Toast>. Most apps should use <Toast.List> for rendering and reach for this hook only when they need custom list control.
Toasts can be swiped away. The direction is auto-detected based on position:
Web: Left/right positioned toasts swipe toward their edge, center toasts swipe horizontally
Native: All positions swipe vertically (up/down) to avoid conflicting with iOS/Android navigation back gesture
When react-native-gesture-handler is set up via @tamagui/native/setup-gesture-handler, the toast uses native gesture coordination (activeOffsetY, failOffsetX) to prevent ScrollView from scrolling while swiping.
Toasts render as in-app Tamagui views on every platform by default. To use the real iOS/Android system toast instead, install Burnt and import its setup module at your app entry:
yarn
npm
bun
pnpm
yarn add burnt
import'@tamagui/native/setup-burnt'
Then opt in on the root:
<Toastnative>{/* ... */}</Toast>
Burnt is resolved lazily, so apps that don’t set it up never load it and never need the native module.
The v2 imperative API (ToastProvider, ToastViewport, useToastController, useToastState) was removed. Replace the provider tree with the composable parts shown above, and call toast() directly instead of going through a controller hook:
// v2
const toast =useToastController()
toast.show('Saved!',{ message:'Your changes are live'})
// v3
toast('Saved!',{ description:'Your changes are live'})
v2-look Toast (v2 composable API): card background/border/radius/padding/shadow/focus outline, styled title/description text, rounded close button, action/cancel buttons, and the styled default toast content wired through Toast.List renderItem, over the unstyled @tamagui/toast behavior (state engine, stacking, drag-to-dismiss, timers). This is the exact source the registry ships — copy it into your app and the examples above import from your copy.
components/tamagui/Toast.tsx
// Styled Toast = the unstyled @tamagui/toast behavior + the default skin,
// layered here in `tamagui`. Single skin definition; the shadcn registry item is
// generated from this file. Because the behavior renders its default toast
// content internally, the styled default content lives HERE and is wired through
// Toast.List's `renderItem`, so the copy-paste registry item is a complete,
// customizable toast (no hidden styling in the behavior package).
//
// Surfaced at both the `tamagui` root (shadowing the unstyled @tamagui/toast
// composable Toast) and the `tamagui/toast` subpath. There is exactly one Toast
// API in v3 — the old imperative ToastProvider/useToastController was removed.
import {
Toast as ToastBehavior,
toast,
useToastItem,
useToasts,
type ExternalToast,
type ToastItemRenderProps,
type ToastListProps,
type ToastPosition,
type ToastRootProps,
type ToastT,
} from '@tamagui/toast'
import {
createRefComponent,
styled,
type TamaguiElement,
withStaticProperties,
} from '@tamagui/core'
import { SizableText } from '@tamagui/text'
import { XStack, YStack } from '@tamagui/stacks'
/* -------------------------------------------------------------------------------------------------
* Styled parts — the default skin over the unstyled @tamagui/toast primitives.
* -----------------------------------------------------------------------------------------------*/
export const ToastItem = styled(ToastBehavior.Item, {
displayName: 'ToastItem',
backgroundColor: 'background',
borderRadius: '6',
paddingHorizontal: '4',
paddingVertical: '3',
borderWidth: 1,
borderColor: 'border-color',
shadowColor: 'rgba(0, 0, 0, 0.15)',
shadowOffset: { width: 0, height: 4 },
shadowRadius: 12,
outlineWidth: 'focus-visible:2px',
outlineColor: 'focus-visible:color-8',
outlineStyle: 'focus-visible:solid',
})
export const ToastTitle = styled(ToastBehavior.Title, {
displayName: 'ToastTitle',
color: 'color',
fontWeight: '600',
size: '4',
})
export const ToastDescription = styled(ToastBehavior.Description, {
displayName: 'ToastDescription',
color: 'color-11',
size: '2',
})
// flat on purpose: the glyph is the whole affordance, so it only brightens on
// hover instead of carrying a chip of its own
export const ToastClose = styled(ToastBehavior.Close, {
displayName: 'ToastClose',
backgroundColor: 'transparent',
color: 'color-9 hover:color press:color',
})
export const ToastAction = styled(ToastBehavior.Action, {
displayName: 'ToastAction',
borderRadius: '2',
paddingHorizontal: '2',
backgroundColor: 'color-5 hover:color-6 press:color-7',
})
/* -------------------------------------------------------------------------------------------------
* ToastDefaultContent — the default toast body (icon / title / description /
* actions / close), styled. Rendered via Toast.List's renderItem below.
* -----------------------------------------------------------------------------------------------*/
function ToastDefaultContent({ toast: t }: { toast: ToastT }) {
const { closeButton } = useToasts()
const { handleClose } = useToastItem()
const dismissible = t.dismissible !== false
const title = typeof t.title === 'function' ? t.title() : t.title
const description =
typeof t.description === 'function' ? t.description() : t.description
return (
<XStack alignItems="flex-start" gap="3">
<ToastBehavior.Icon />
<YStack flex={1} gap="1">
{title ? <ToastTitle>{title}</ToastTitle> : null}
{description ? <ToastDescription>{description}</ToastDescription> : null}
{t.action || t.cancel ? (
<XStack gap="2" marginTop="2">
{t.cancel ? (
<ToastAction
backgroundColor="transparent"
onPress={(e: any) => {
t.cancel?.onClick?.(e)
handleClose()
}}
>
<SizableText size="2" color="color-11">
{t.cancel.label}
</SizableText>
</ToastAction>
) : null}
{t.action ? (
<ToastAction
backgroundColor="color-11 hover:color-10 press:color-9"
onPress={(e: any) => {
t.action?.onClick?.(e)
if (!(e as any).defaultPrevented) {
handleClose()
}
}}
>
<SizableText size="2" fontWeight="600" color="background">
{t.action.label}
</SizableText>
</ToastAction>
) : null}
</XStack>
) : null}
</YStack>
{closeButton && dismissible ? <ToastClose /> : null}
</XStack>
)
}
const renderStyledToast = ({ toast: t, index }: ToastItemRenderProps) => (
<ToastItem toast={t} index={index}>
<ToastDefaultContent toast={t} />
</ToastItem>
)
// Capture the behavior parts up front. `withStaticProperties` assigns onto the
// component it is given, so composing directly onto ToastBehavior would rewrite
// @tamagui/toast's own Toast.List/.Item for every consumer of the unstyled
// package — and ToastList below would end up rendering itself.
const BehaviorList = ToastBehavior.List
const BehaviorViewport = ToastBehavior.Viewport
const BehaviorIcon = ToastBehavior.Icon
// Styled list — defaults renderItem to the styled default content so a bare
// `<Toast.List />` is styled; a consumer-supplied renderItem still overrides it.
function ToastList(props: ToastListProps) {
return <BehaviorList renderItem={renderStyledToast} {...props} />
}
/* -------------------------------------------------------------------------------------------------
* Toast — the styled composable API (styled parts on the behavior root).
* -----------------------------------------------------------------------------------------------*/
// A distinct root, so the styled parts hang off this skin instead of mutating
// the shared behavior component (see BehaviorList above).
const ToastRoot = createRefComponent<TamaguiElement, ToastRootProps>(
function Toast(props, ref) {
return <ToastBehavior {...props} ref={ref} />
}
)
export const Toast = withStaticProperties(ToastRoot, {
Viewport: BehaviorViewport,
List: ToastList,
Item: ToastItem,
Title: ToastTitle,
Description: ToastDescription,
Close: ToastClose,
Action: ToastAction,
Icon: BehaviorIcon,
})
// Re-export the non-visual surface so `tamagui/toast` is a drop-in for the
// styled toast: imperative API, render hooks, and the public types.
export { toast, useToastItem, useToasts }
export type { ExternalToast, ToastPosition, ToastT }
Expects theme tokens: background, border-color, color, color-3, color-4, color-5, color-6, color-7, color-8, color-10, color-11, color-11. Native: requires a Portal provider at the app root for the toast viewport to mount above content
Need raw behavior without any skin? tamagui/unstyled re-exports the @tamagui/ui primitives (advanced).