<BreadLoaf /> component to your root layout file.
Basic setup
Add<BreadLoaf /> to your root layout file (app/_layout.tsx):
app/_layout.tsx
import { Stack } from 'expo-router';
import { BreadLoaf } from 'react-native-bread';
export default function RootLayout() {
return (
<>
<Stack>
<Stack.Screen name="index" />
</Stack>
<BreadLoaf />
</>
);
}
app/index.tsx
import { View, Button } from 'react-native';
import { toast } from 'react-native-bread';
export default function HomeScreen() {
return (
<View>
<Button
title="Show Toast"
onPress={() => toast.success('Hello from Expo Router!')}
/>
</View>
);
}
With tabs navigator
For apps using tabs, place<BreadLoaf /> in the root layout, not the tabs layout:
app/_layout.tsx
import { Stack } from 'expo-router';
import { BreadLoaf } from 'react-native-bread';
export default function RootLayout() {
return (
<>
<Stack>
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
</Stack>
<BreadLoaf />
</>
);
}
app/(tabs)/_layout.tsx
import { Tabs } from 'expo-router';
export default function TabLayout() {
return (
<Tabs>
<Tabs.Screen name="index" options={{ title: 'Home' }} />
<Tabs.Screen name="profile" options={{ title: 'Profile' }} />
<Tabs.Screen name="settings" options={{ title: 'Settings' }} />
</Tabs>
);
}
Place
<BreadLoaf /> in your root layout (usually app/_layout.tsx), not in nested layouts. This ensures toasts appear consistently across all screens.With modals
Expo Router supports modal presentation. Add<BreadLoaf /> to the root and configure modal screens:
app/_layout.tsx
import { Stack } from 'expo-router';
import { BreadLoaf } from 'react-native-bread';
import { Platform } from 'react-native';
export default function RootLayout() {
return (
<>
<Stack>
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
<Stack.Screen
name="modal"
options={{
presentation: Platform.OS === 'android' ? 'containedModal' : 'modal',
title: 'Modal Screen',
}}
/>
</Stack>
<BreadLoaf />
</>
);
}
app/modal.tsx
import { View, Button, StyleSheet } from 'react-native';
import { toast } from 'react-native-bread';
import { router } from 'expo-router';
export default function ModalScreen() {
return (
<View style={styles.container}>
<Button
title="Show Toast"
onPress={() => toast.success('Toast in modal!')}
/>
<Button title="Close" onPress={() => router.back()} />
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
gap: 16,
},
});
With configuration
Customize toast behavior via theconfig prop:
app/_layout.tsx
import { Stack } from 'expo-router';
import { BreadLoaf } from 'react-native-bread';
export default function RootLayout() {
return (
<>
<Stack>
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
</Stack>
<BreadLoaf
config={{
position: 'bottom',
defaultDuration: 5000,
maxStack: 3,
colors: {
success: {
accent: '#22c55e',
background: '#f0fdf4',
},
error: {
accent: '#ef4444',
background: '#fef2f2',
},
},
}}
/>
</>
);
}
Complete example
Here’s a full Expo Router app structure with tabs, modals, and toasts:app/_layout.tsx
import { Stack } from 'expo-router';
import { BreadLoaf } from 'react-native-bread';
import { Platform } from 'react-native';
export default function RootLayout() {
return (
<>
<Stack>
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
<Stack.Screen
name="(modal)"
options={{
presentation: Platform.OS === 'android' ? 'containedModal' : 'modal'
}}
/>
<Stack.Screen name="+not-found" />
</Stack>
<BreadLoaf
config={{
position: 'top',
stacking: true,
maxStack: 3,
defaultDuration: 4000,
}}
/>
</>
);
}
app/(tabs)/_layout.tsx
import { Tabs } from 'expo-router';
import { IconSymbol } from '@/components/ui/IconSymbol';
export default function TabLayout() {
return (
<Tabs screenOptions={{ headerShown: false }}>
<Tabs.Screen
name="index"
options={{
title: 'Home',
tabBarIcon: ({ color }) => <IconSymbol name="house.fill" color={color} />,
}}
/>
<Tabs.Screen
name="explore"
options={{
title: 'Explore',
tabBarIcon: ({ color }) => <IconSymbol name="paperplane.fill" color={color} />,
}}
/>
</Tabs>
);
}
app/(tabs)/index.tsx
import { View, Button, StyleSheet } from 'react-native';
import { toast } from 'react-native-bread';
import { router } from 'expo-router';
export default function HomeScreen() {
return (
<View style={styles.container}>
<Button
title="Success Toast"
onPress={() => toast.success('Success!', 'Operation completed')}
/>
<Button
title="Error Toast"
onPress={() => toast.error('Error!', 'Something went wrong')}
/>
<Button
title="Promise Toast"
onPress={async () => {
await toast.promise(
fetch('/api/data').then(r => r.json()),
{
loading: 'Loading data...',
success: 'Data loaded!',
error: 'Failed to load data',
}
);
}}
/>
<Button
title="Open Modal"
onPress={() => router.push('/modal')}
/>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
gap: 16,
},
});
app/(tabs)/explore.tsx
import { View, Text, Button, StyleSheet } from 'react-native';
import { toast } from 'react-native-bread';
export default function ExploreScreen() {
return (
<View style={styles.container}>
<Text style={styles.title}>Explore Screen</Text>
<Button
title="Custom Toast"
onPress={() => {
toast.custom(
({ dismiss }) => (
<View style={styles.customToast}>
<Text style={styles.customText}>Custom notification!</Text>
<Button title="Dismiss" onPress={dismiss} />
</View>
),
{ duration: 5000 }
);
}}
/>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
gap: 16,
},
title: {
fontSize: 24,
fontWeight: 'bold',
marginBottom: 16,
},
customToast: {
padding: 16,
backgroundColor: '#fff',
borderRadius: 12,
flexDirection: 'row',
alignItems: 'center',
gap: 12,
},
customText: {
flex: 1,
fontSize: 16,
},
});
app/(modal)/_layout.tsx
import { Stack } from 'expo-router';
import { ToastPortal } from 'react-native-bread';
export default function ModalLayout() {
return (
<>
<Stack>
<Stack.Screen name="index" options={{ title: 'Modal' }} />
</Stack>
<ToastPortal />
</>
);
}
app/(modal)/index.tsx
import { View, Button, Text, StyleSheet } from 'react-native';
import { toast } from 'react-native-bread';
import { router } from 'expo-router';
export default function ModalScreen() {
const handleSave = async () => {
const result = await toast.promise(
saveData(),
{
loading: 'Saving...',
success: 'Saved successfully!',
error: 'Failed to save',
}
);
if (result.success) {
router.back();
}
};
return (
<View style={styles.container}>
<Text style={styles.title}>Modal Screen</Text>
<Button title="Save" onPress={handleSave} />
<Button title="Cancel" onPress={() => router.back()} />
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
gap: 16,
padding: 16,
},
title: {
fontSize: 20,
fontWeight: '600',
marginBottom: 24,
},
});
async function saveData() {
// Simulate API call
await new Promise(resolve => setTimeout(resolve, 2000));
return { success: true };
}
Common patterns
- Navigation with toasts
- Form submission
- Tab-specific toasts
Show a toast and navigate after a successful action:
import { router } from 'expo-router';
import { toast } from 'react-native-bread';
const handleDelete = async () => {
const result = await toast.promise(
deleteItem(itemId),
{
loading: 'Deleting...',
success: 'Item deleted',
error: 'Failed to delete',
}
);
if (result.success) {
router.back();
}
};
Handle form submission with loading states:
import { router } from 'expo-router';
import { toast } from 'react-native-bread';
const handleSubmit = async (formData: FormData) => {
const result = await toast.promise(
submitForm(formData),
{
loading: {
title: 'Submitting...',
description: 'Please wait',
},
success: {
title: 'Success!',
description: 'Form submitted successfully',
},
error: (err) => ({
title: 'Submission failed',
description: err.message,
}),
}
);
if (result.success) {
router.push('/success');
}
};
Show toasts from any tab screen:
// app/(tabs)/profile.tsx
import { View, Button } from 'react-native';
import { toast } from 'react-native-bread';
export default function ProfileScreen() {
return (
<View>
<Button
title="Update Profile"
onPress={async () => {
await toast.promise(
updateProfile(),
{
loading: 'Updating profile...',
success: 'Profile updated!',
error: 'Failed to update',
}
);
}}
/>
</View>
);
}
Troubleshooting
Toasts don't appear
Toasts don't appear
Make sure
<BreadLoaf /> is placed in your root layout (app/_layout.tsx), not in a nested layout or screen file.// ✅ Correct
// app/_layout.tsx
export default function RootLayout() {
return (
<>
<Stack />
<BreadLoaf />
</>
);
}
// ❌ Incorrect
// app/(tabs)/_layout.tsx
export default function TabLayout() {
return (
<>
<Tabs />
<BreadLoaf /> {/* Too nested */}
</>
);
}
Toasts hidden behind modal on Android
Toasts hidden behind modal on Android
Use
containedModal presentation or add ToastPortal to your modal layout. See the Modal setup guide for details.// Option 1: containedModal
<Stack.Screen
name="modal"
options={{ presentation: Platform.OS === 'android' ? 'containedModal' : 'modal' }}
/>
// Option 2: ToastPortal in modal layout
// app/(modal)/_layout.tsx
export default function ModalLayout() {
return (
<>
<Stack />
<ToastPortal />
</>
);
}
Different toast styles across screens
Different toast styles across screens
Toast configuration is global. Make sure you’re only setting config once in your root layout:
// ✅ Configure once
// app/_layout.tsx
<BreadLoaf config={{ position: 'bottom' }} />
// ❌ Don't configure in multiple places
// app/(tabs)/_layout.tsx
<BreadLoaf config={{ position: 'top' }} /> {/* Don't do this */}
Next steps
Modal setup
Configure toasts for modal screens on Android
Custom toasts
Create fully custom toast designs
Promise toasts
Handle async operations with automatic state transitions
RTL support
Configure right-to-left layout support