component. -->
A concise message that appears briefly.
Install the component via your command line.
npx expo install @rn-primitives/toast
Copy/paste the following code to ~/components/primitives/toast/index.tsx
~/components/primitives/toast/index.tsx
import * as Slot from '~/components/primitives/slot';import * as React from 'react';import { Pressable, Text, View, type GestureResponderEvent } from 'react-native';import type { ActionProps, ActionRef, CloseProps, CloseRef, DescriptionProps, DescriptionRef, RootProps, RootRef, TitleProps, TitleRef,} from './types'; interface RootContext extends RootProps { nativeID: string;}const ToastContext = React.createContext<RootContext | null>(null); const Root = React.forwardRef<RootRef, RootProps>( ({ asChild, type = 'foreground', open, onOpenChange, ...viewProps }, ref) => { const nativeID = React.useId(); if (!open) { return null; } const Component = asChild ? Slot.View : View; return ( <ToastContext.Provider value={{ open, onOpenChange, type, nativeID, }} > <Component ref={ref} role='status' aria-live={type === 'foreground' ? 'assertive' : 'polite'} {...viewProps} /> </ToastContext.Provider> ); }); Root.displayName = 'RootToast'; function useToastContext() { const context = React.useContext(ToastContext); if (!context) { throw new Error('Toast compound components cannot be rendered outside the Toast component'); } return context;} const Close = React.forwardRef<CloseRef, CloseProps>( ({ asChild, onPress: onPressProp, disabled = false, ...props }, ref) => { const { onOpenChange } = useToastContext(); function onPress(ev: GestureResponderEvent) { if (disabled) return; onOpenChange(false); onPressProp?.(ev); } const Component = asChild ? Slot.Pressable : Pressable; return ( <Component ref={ref} aria-disabled={disabled ?? undefined} role='button' onPress={onPress} disabled={disabled ?? undefined} {...props} /> ); }); Close.displayName = 'CloseToast'; const Action = React.forwardRef<ActionRef, ActionProps>( ({ asChild, onPress: onPressProp, disabled = false, ...props }, ref) => { const { onOpenChange } = useToastContext(); function onPress(ev: GestureResponderEvent) { if (disabled) return; onOpenChange(false); onPressProp?.(ev); } const Component = asChild ? Slot.Pressable : Pressable; return ( <Component ref={ref} aria-disabled={disabled ?? undefined} role='button' onPress={onPress} disabled={disabled ?? undefined} {...props} /> ); }); Action.displayName = 'ActionToast'; const Title = React.forwardRef<TitleRef, TitleProps>(({ asChild, ...props }, ref) => { const { nativeID } = useToastContext(); const Component = asChild ? Slot.Text : Text; return <Component ref={ref} role='heading' nativeID={`${nativeID}_label`} {...props} />;}); Title.displayName = 'TitleToast'; const Description = React.forwardRef<DescriptionRef, DescriptionProps>( ({ asChild, ...props }, ref) => { const { nativeID } = useToastContext(); const Component = asChild ? Slot.Text : Text; return <Component ref={ref} nativeID={`${nativeID}_desc`} {...props} />; }); Description.displayName = 'DescriptionToast'; export { Action, Close, Description, Root, Title };
Copy/paste the following code to ~/components/primitives/toast/types.ts
~/components/primitives/toast/types.ts
import type { PressableRef, SlottablePressableProps, SlottableTextProps, SlottableViewProps, TextRef, ViewRef,} from '~/components/primitives/types'; type RootProps = SlottableViewProps & { open: boolean; onOpenChange: (value: boolean) => void; type?: 'foreground' | 'background';}; type CloseProps = SlottablePressableProps;type ActionProps = SlottablePressableProps;type TitleProps = SlottableTextProps;type DescriptionProps = SlottableTextProps; type RootRef = ViewRef;type CloseRef = PressableRef;type ActionRef = PressableRef;type TitleRef = TextRef;type DescriptionRef = TextRef; export type { ActionProps, ActionRef, CloseProps, CloseRef, DescriptionProps, DescriptionRef, RootProps, RootRef, TitleProps, TitleRef,};
import * React from 'react';import { Pressable, Text, View } from 'react-native';import { useSafeAreaInsets } from 'react-native-safe-area-context';import { Portal } from '@rn-primitives/portal';import * as ToastPrimitive from '@rn-primitives/toast'; function Example() { const [open, setOpen] = React.useState(false); const [seconds, setSeconds] = React.useState(3); const insets = useSafeAreaInsets(); React.useEffect(() => { let interval: ReturnType<typeof setInterval> | null = null; if (open) { interval = setInterval(() => { setSeconds((prevSeconds) => { if (prevSeconds <= 1) { setOpen(false); if (interval) { clearInterval(interval); } return 3; } return prevSeconds - 1; }); }, 1000); } else { if (interval) { clearInterval(interval); } setSeconds(3); } if (interval && !open) { clearInterval(interval); } return () => { if (interval) { clearInterval(interval); } }; }, [open, seconds]); return ( <> {open && ( <Portal name='toast-example'> <View style={{ top: insets.top + 4 }} className='px-4 absolute w-full'> <ToastPrimitive.Root type='foreground' open={open} onOpenChange={setOpen} className='opacity-95 bg-secondary border-border flex-row justify-between items-center p-4 rounded-xl' > <View className='gap-1.5'> <ToastPrimitive.Title className='text-foreground text-3xl'>Here is a toast</ToastPrimitive.Title> <ToastPrimitive.Description className='text-foreground text-lg'> It will disappear in {seconds} seconds </ToastPrimitive.Description> </View> <View className='gap-2'> <ToastPrimitive.Action className='border border-primary px-4 py-2'> <Text className='text-foreground'>Action</Text> </ToastPrimitive.Action> <ToastPrimitive.Close className='border border-primary px-4 py-2'> <Text className='text-foreground'>Close</Text> </ToastPrimitive.Close> </View> </ToastPrimitive.Root> </View> </Portal> )} <View className='flex-1 justify-center items-center p-6 gap-12'> <Pressable onPress={() => setOpen((prev) => !prev)}> <Text className='text-foreground text-xl'>Show Toast</Text> </Pressable> </View> </> );}
Extends View props
View
foreground
Extends Pressable props
Pressable
Extends Text props
Text