Skip to main content
Custom toasts let you render any React component inside a toast container. Your content receives all the built-in animations (entry, exit, stacking) while you maintain full control over the layout and styling.

Basic usage

Use toast.custom() to render your own content:
import { toast } from 'react-native-bread';
import { View, Text } from 'react-native';

toast.custom(
  <View style={{ padding: 16, backgroundColor: '#fff', borderRadius: 12 }}>
    <Text style={{ fontSize: 16, fontWeight: '600' }}>Custom notification</Text>
    <Text style={{ color: '#666', marginTop: 4 }}>This is completely custom!</Text>
  </View>
);

Using a render function

For interactive toasts, use a render function to access dismiss and other props:
toast.custom(({ dismiss, id, type, isExiting }) => (
  <View style={{ flexDirection: 'row', padding: 16, backgroundColor: '#fff' }}>
    <View style={{ flex: 1 }}>
      <Text style={{ fontWeight: '600' }}>New message</Text>
      <Text style={{ color: '#666' }}>Hey, check this out!</Text>
    </View>
    <Pressable onPress={dismiss}>
      <Text style={{ color: '#3b82f6' }}>Dismiss</Text>
    </Pressable>
  </View>
));

Render function props

The render function receives a CustomContentProps object:
interface CustomContentProps {
  /** Unique toast ID */
  id: string;
  /** Function to dismiss this toast */
  dismiss: () => void;
  /** Toast type (info by default, or specify via options) */
  type: ToastType;
  /** Whether the toast is currently animating out */
  isExiting: boolean;
}

Configuration options

Pass an options object as the second argument:
toast.custom(
  <MyCustomToast />,
  {
    duration: 5000,
    dismissible: false,
    showCloseButton: false,
    style: { borderRadius: 16 },
    type: 'success', // Affects default colors if needed
  }
);
When using customContent, the standard toast props (icon, title, description, titleStyle, descriptionStyle) are ignored. You have complete control over the content.

Real-world examples

Message notification with avatar

import { Image, View, Text, Pressable } from 'react-native';

const showMessageNotification = (message: Message) => {
  toast.custom(
    ({ dismiss }) => (
      <View
        style={{
          flexDirection: 'row',
          padding: 16,
          backgroundColor: '#fff',
          borderRadius: 12,
          gap: 12,
          alignItems: 'center',
          shadowColor: '#000',
          shadowOffset: { width: 0, height: 2 },
          shadowOpacity: 0.1,
          shadowRadius: 8,
        }}
      >
        <Image
          source={{ uri: message.avatar }}
          style={{ width: 44, height: 44, borderRadius: 22 }}
        />
        <View style={{ flex: 1 }}>
          <Text style={{ fontWeight: '600', fontSize: 15 }}>
            {message.sender}
          </Text>
          <Text style={{ color: '#666', marginTop: 2 }} numberOfLines={2}>
            {message.text}
          </Text>
        </View>
        <Pressable onPress={dismiss}>
          <Text style={{ color: '#3b82f6', fontWeight: '500' }}>View</Text>
        </Pressable>
      </View>
    ),
    { duration: 6000 }
  );
};

Progress upload toast

import { View, Text, ActivityIndicator } from 'react-native';
import { useState } from 'react';

const showUploadProgress = () => {
  toast.custom(
    ({ dismiss }) => {
      const [progress, setProgress] = useState(0);

      // Update progress (you'd connect this to your actual upload)
      useEffect(() => {
        const interval = setInterval(() => {
          setProgress(p => {
            if (p >= 100) {
              clearInterval(interval);
              dismiss();
              return 100;
            }
            return p + 10;
          });
        }, 500);
        return () => clearInterval(interval);
      }, []);

      return (
        <View
          style={{
            padding: 16,
            backgroundColor: '#fff',
            borderRadius: 12,
            width: 280,
          }}
        >
          <View style={{ flexDirection: 'row', alignItems: 'center', gap: 12 }}>
            <ActivityIndicator />
            <Text style={{ flex: 1, fontWeight: '600' }}>Uploading...</Text>
            <Text style={{ color: '#666' }}>{progress}%</Text>
          </View>
          <View
            style={{
              height: 4,
              backgroundColor: '#f0f0f0',
              borderRadius: 2,
              marginTop: 12,
              overflow: 'hidden',
            }}
          >
            <View
              style={{
                height: '100%',
                width: `${progress}%`,
                backgroundColor: '#3b82f6',
              }}
            />
          </View>
        </View>
      );
    },
    {
      duration: 60000,
      dismissible: false,
    }
  );
};

Action toast with buttons

import { View, Text, Pressable } from 'react-native';

const showActionToast = (item: Item) => {
  toast.custom(
    ({ dismiss }) => (
      <View
        style={{
          padding: 16,
          backgroundColor: '#1f2937',
          borderRadius: 12,
        }}
      >
        <Text style={{ color: '#fff', fontWeight: '600', marginBottom: 12 }}>
          Delete "{item.name}"?
        </Text>
        <View style={{ flexDirection: 'row', gap: 8, justifyContent: 'flex-end' }}>
          <Pressable
            onPress={() => {
              dismiss();
            }}
            style={{
              paddingHorizontal: 16,
              paddingVertical: 8,
              borderRadius: 6,
            }}
          >
            <Text style={{ color: '#9ca3af', fontWeight: '500' }}>Cancel</Text>
          </Pressable>
          <Pressable
            onPress={() => {
              deleteItem(item.id);
              dismiss();
              toast.success('Deleted', 'Item removed from your library');
            }}
            style={{
              paddingHorizontal: 16,
              paddingVertical: 8,
              backgroundColor: '#ef4444',
              borderRadius: 6,
            }}
          >
            <Text style={{ color: '#fff', fontWeight: '500' }}>Delete</Text>
          </Pressable>
        </View>
      </View>
    ),
    {
      duration: 10000,
    }
  );
};

Rich media toast

import { View, Text, Image } from 'react-native';

const showMediaToast = (media: MediaItem) => {
  toast.custom(
    <View style={{ backgroundColor: '#fff', borderRadius: 12, overflow: 'hidden' }}>
      <Image
        source={{ uri: media.thumbnail }}
        style={{ width: '100%', height: 160 }}
        resizeMode="cover"
      />
      <View style={{ padding: 16 }}>
        <Text style={{ fontWeight: '600', fontSize: 16 }}>{media.title}</Text>
        <Text style={{ color: '#666', marginTop: 4 }}>{media.description}</Text>
        <View style={{ flexDirection: 'row', marginTop: 12, gap: 8 }}>
          <View style={{ paddingHorizontal: 12, paddingVertical: 6, backgroundColor: '#f3f4f6', borderRadius: 6 }}>
            <Text style={{ fontSize: 12, color: '#666' }}>{media.duration}</Text>
          </View>
          <View style={{ paddingHorizontal: 12, paddingVertical: 6, backgroundColor: '#f3f4f6', borderRadius: 6 }}>
            <Text style={{ fontSize: 12, color: '#666' }}>{media.type}</Text>
          </View>
        </View>
      </View>
    </View>,
    { duration: 8000 }
  );
};

Undo action toast

const showUndoToast = (deletedItem: Item) => {
  let canUndo = true;

  const toastId = toast.custom(
    ({ dismiss }) => (
      <View
        style={{
          flexDirection: 'row',
          alignItems: 'center',
          padding: 16,
          backgroundColor: '#1f2937',
          borderRadius: 12,
          gap: 12,
        }}
      >
        <Text style={{ flex: 1, color: '#fff' }}>Item deleted</Text>
        <Pressable
          onPress={() => {
            if (canUndo) {
              restoreItem(deletedItem);
              dismiss();
              toast.success('Restored', 'Item restored to your library');
            }
          }}
          style={{
            paddingHorizontal: 12,
            paddingVertical: 6,
            backgroundColor: '#3b82f6',
            borderRadius: 6,
          }}
        >
          <Text style={{ color: '#fff', fontWeight: '500' }}>Undo</Text>
        </Pressable>
      </View>
    ),
    {
      duration: 5000,
    }
  );

  // After 5 seconds, permanently delete
  setTimeout(() => {
    canUndo = false;
    permanentlyDelete(deletedItem);
  }, 5000);
};

Styling tips

Your custom content fills the entire toast container. Add padding, background, and border radius to your root element:
toast.custom(
  <View
    style={{
      padding: 16,
      backgroundColor: '#fff',
      borderRadius: 12,
      // Shadow for iOS
      shadowColor: '#000',
      shadowOffset: { width: 0, height: 2 },
      shadowOpacity: 0.1,
      shadowRadius: 8,
      // Shadow for Android
      elevation: 4,
    }}
  >
    {/* Your content */}
  </View>
);

Using with the options object

You can apply the same options available for standard toasts:
toast.custom(
  <MyComponent />,
  {
    // Toast behavior
    duration: 5000,
    dismissible: true,
    showCloseButton: false, // Usually false for custom content
    
    // Styling (applied to the container wrapper)
    style: {
      borderRadius: 16,
      // Note: Your custom content should handle its own background/padding
    },
    
    // Type affects animation color/behavior
    type: 'success', // 'success' | 'error' | 'info' | 'loading'
    
    // Deduplication
    id: 'my-custom-toast',
    deduplication: true,
  }
);
The icon, title, description, titleStyle, and descriptionStyle options are ignored when using customContent. These are only applicable to standard toasts.

TypeScript support

Full type safety for custom toast render functions:
import { CustomContentRenderFn } from 'react-native-bread';

const MyToastContent: CustomContentRenderFn = ({ dismiss, id, type, isExiting }) => {
  // Full type inference for props
  return (
    <View>
      <Text>Toast ID: {id}</Text>
      <Text>Type: {type}</Text>
      <Pressable onPress={dismiss}>
        <Text>Close</Text>
      </Pressable>
    </View>
  );
};

toast.custom(MyToastContent);

API reference

toast.custom()

function custom(
  content: ReactNode | CustomContentRenderFn,
  options?: CustomToastOptions
): string

Parameters

  • content: React component or render function
  • options: Configuration object (all optional)

CustomToastOptions

interface CustomToastOptions {
  type?: ToastType;           // 'success' | 'error' | 'info' | 'loading'
  duration?: number;          // Duration in ms
  dismissible?: boolean;      // Allow swipe to dismiss
  showCloseButton?: boolean;  // Show close button (usually false)
  style?: ViewStyle;          // Container style overrides
  id?: string;                // Stable ID for deduplication
  deduplication?: boolean;    // Enable deduplication
}

CustomContentRenderFn

type CustomContentRenderFn = (props: CustomContentProps) => ReactNode

interface CustomContentProps {
  id: string;           // Toast ID
  dismiss: () => void;  // Function to dismiss this toast
  type: ToastType;      // Toast type
  isExiting: boolean;   // Whether toast is animating out
}

Return value

Returns the toast ID (string) which can be used with toast.dismiss(id).

Build docs developers (and LLMs) love