Toast

Accessible toasts with stacking, gestures, and animations

Toast presents time-sensitive messages with accessible announcements, stacking, gestures, and animated entry or exit.

Features

  • Composable component API

  • Smooth stacking with hover-to-expand

  • Swipe dismissal with spring physics

  • Promise toasts for async operations

  • All Tamagui animation drivers supported

Installation

Toast ships with tamagui: there’s nothing extra to install:

import { Toast, toast } from 'tamagui/toast'

If you’re using the unstyled primitives directly, it’s also published on its own:

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.

import { Button } from 'tamagui/button'
import { Toast, toast } from 'tamagui/toast'
function App() {
return (
<Toast position="bottom-right">
<Toast.Viewport>
<Toast.List />
</Toast.Viewport>
<Button onPress={() => toast('Hello!')}>Show Toast</Button>
</Toast>
)
}

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.List renderItem={({ toast: t, index }) => ( <Toast.Item key={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 */}
<Toast.Title />
<Toast.Description />
<Toast.Close />
<Toast.Action />
</Toast.Item>
</Toast.Viewport>
</Toast>

API reference

toast()

The imperative toast function:

// Basic
toast('Hello world')
// With types
toast.success('Saved!')
toast.error('Failed')
toast.warning('Careful')
toast.info('FYI')
toast.loading('Processing...')
// With options
toast('Message', {
description: 'More details here',
duration: 5000,
})
// Dismiss
const id = toast('Hello')
toast.dismiss(id) // dismiss specific
toast.dismiss() // dismiss all

toast.promise()

For async operations:

toast.promise(saveData(), {
loading: 'Saving...',
success: 'Saved!',
error: 'Failed to save',
})

Toast (Root)

Props

  • position

    'top-left' | 'top-center' | 'top-right' | 'bottom-left' | 'bottom-center' | 'bottom-right'

    Default: 

    'bottom-right'

    Position of the toast viewport.

  • duration

    number

    Default: 

    4000

    Default toast duration in milliseconds.

  • gap

    number

    Default: 

    14

    Gap between toasts when expanded.

  • visibleToasts

    number

    Default: 

    4

    Maximum number of visible toasts.

  • swipeDirection

    'auto' | 'left' | 'right' | 'up' | 'down'

    Default: 

    'auto'

    Swipe direction to dismiss. 'auto' detects based on position.

  • closeButton

    boolean

    Default: 

    false

    Show close button on toasts.

  • native

    boolean

    Default: 

    false

    Use the native OS toast on iOS/Android instead of in-app views. Requires burnt.

  • Toast.Viewport

    The portal container for toasts. Extends View.

    Props

  • offset

    number | { top?: number, right?: number, bottom?: number, left?: number }

    Default: 

    16

    Offset from screen edge.

  • hotkey

    string[]

    Default: 

    ['altKey', 'KeyT']

    Hotkey to focus viewport.

  • label

    string

    Default: 

    'Notifications'

    Aria label for the viewport.

  • Toast.Item

    Individual toast wrapper with stacking and drag support. Extends View.

    Props

  • toast (required)

    ToastT

    The toast data object.

  • index (required)

    number

    Index in the toast list (for stacking).

  • Supports render prop for custom content:

    <Toast.Item toast={t} index={i}>
    {({ toast, handleClose }) => (
    <YStack>
    <Toast.Title>{toast.title}</Toast.Title>
    <Button onPress={handleClose}>Close</Button>
    </YStack>
    )}
    </Toast.Item>

    Toast.Title

    Styled text for the toast title. Extends SizableText.

    Toast.Description

    Styled text for the toast description. Extends SizableText.

    Toast.Close

    Close button. Extends View.

    Toast.Action

    Action button. Extends View.

    useToasts

    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.

    const { toasts, expanded, position } = useToasts()

    Features

    Stacking

    Multiple toasts stack visually. Hover over the stack to expand and see all toasts.

    Swipe to dismiss

    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.

    Native toasts

    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 add burnt
    import '@tamagui/native/setup-burnt'

    Then opt in on the root:

    <Toast native>{/* ... */}</Toast>

    Burnt is resolved lazily, so apps that don’t set it up never load it and never need the native module.

    Native safe area

    On native, toasts portal to root and automatically respect safe area insets (status bar, Dynamic Island, home indicator) when configured:

    import { SafeAreaProvider } from 'react-native-safe-area-context'
    import { useSafeAreaInsets } from 'react-native-safe-area-context'
    import { TamaguiProvider } from 'tamagui'
    function App() {
    return (
    <SafeAreaProvider>
    <AppInner />
    </SafeAreaProvider>
    )
    }
    function AppInner() {
    const insets = useSafeAreaInsets()
    return (
    <TamaguiProvider config={config} insets={insets}>
    {/* toasts will automatically avoid status bar and home indicator */}
    </TamaguiProvider>
    )
    }

    The toast reads insets from TamaguiProvider via useConfiguration().insets, the same mechanism used by other Tamagui components like Slider.

    Custom styling

    Each part can be styled independently:

    <Toast.Item toast={t} index={i} backgroundColor="blue-100" borderRadius="6">
    <Toast.Title color="blue-950" fontWeight="bold">
    {t.title}
    </Toast.Title>
    <Toast.Description color="blue-800">
    {t.description}
    </Toast.Description>
    </Toast.Item>

    Migrating from v2

    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' })

    See the upgrade guide for the full migration.