Basic usage
Usetoast.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 aCustomContentProps 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
- Container styles
- Layout patterns
- Dark mode
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>
);
Common layouts for custom toasts:
// Horizontal layout with icon
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 12 }}>
<Icon />
<View style={{ flex: 1 }}>
<Text>Title</Text>
<Text>Description</Text>
</View>
<Button />
</View>
// Vertical stacked
<View>
<Image style={{ width: '100%', height: 160 }} />
<View style={{ padding: 16 }}>
<Text>Title</Text>
<Text>Description</Text>
</View>
</View>
// Split with action
<View>
<View style={{ padding: 16 }}>
<Text>Message</Text>
</View>
<View style={{ flexDirection: 'row', borderTopWidth: 1, borderTopColor: '#e5e7eb' }}>
<Pressable style={{ flex: 1, padding: 12 }}>
<Text>Cancel</Text>
</Pressable>
<Pressable style={{ flex: 1, padding: 12 }}>
<Text>Confirm</Text>
</Pressable>
</View>
</View>
Adapt your custom toast to the current theme:
import { useColorScheme } from 'react-native';
const showCustomToast = () => {
const isDark = useColorScheme() === 'dark';
toast.custom(
<View
style={{
padding: 16,
backgroundColor: isDark ? '#1f2937' : '#fff',
borderRadius: 12,
}}
>
<Text style={{ color: isDark ? '#fff' : '#000' }}>
Theme-aware toast
</Text>
</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 withtoast.dismiss(id).