Skip to content

AlshehriAli0/react-native-bread

Repository files navigation

React Native Bread 🍞

An extremely lightweight, opinionated toast component for React Native.

demo

Features

  • Lightweight - only 20KB packed size
  • New Architecture - built exclusively for React Native 0.76+ with Fabric
  • Clean, imperative API inspired by Sonner
  • Zero setup - add one component, start toasting. No hooks, no providers
  • Smooth 60fps animations powered by Reanimated
  • Natural swipe gestures that feel native to the platform
  • Multiple toast types: success, error, info, promise, and custom
  • Promise handling with automatic loading → success/error states
  • Toast stacking with configurable limits
  • Works above modals - automatic on iOS, simple setup on Android
  • RTL support - perfect for Arabic and other RTL languages
  • Completely customizable - colors, icons, styles, animations
  • Full Expo compatibility

Installation

bun add react-native-bread

Peer Dependencies

This package requires the following peer dependencies:

Package Version
react-native-reanimated >= 4.1.0
react-native-gesture-handler >= 2.25.0
react-native-safe-area-context >= 5.0.0
react-native-screens >= 4.0.0
react-native-svg >= 15.8.0
react-native-worklets >= 0.5.0

If you don't have these installed, you can install all peer dependencies at once:

bun add react-native-reanimated react-native-gesture-handler react-native-safe-area-context react-native-screens react-native-svg react-native-worklets

Or with npm:

npm install react-native-reanimated react-native-gesture-handler react-native-safe-area-context react-native-screens react-native-svg react-native-worklets

Note: Make sure your react-native-reanimated and react-native-worklets versions are compatible. Reanimated 4.1.x works with worklets 0.5.x-0.7.x, while Reanimated 4.2.x requires worklets 0.7.x only.

Usage

In your App.tsx/entry point

import { BreadLoaf } from 'react-native-bread';

function App() {
  return (
    <View>
      <NavigationContainer>...</NavigationContainer>
      <BreadLoaf />
    </View>
  );
}

Expo Router

When using Expo Router, place the BreadLoaf component in your root layout file (app/_layout.tsx):

import { BreadLoaf } from 'react-native-bread';
import { Stack } from 'expo-router';

export default function RootLayout() {
  return (
    <>
      <Stack>
        <Stack.Screen name="(tabs)" options={{ headerShown: false }} />
        <Stack.Screen name="+not-found" />
      </Stack>
      <BreadLoaf />
    </>
  );
}

This ensures the toasts will be displayed across all screens in your app.

Show a toast

import { toast } from 'react-native-bread';

// Basic usage
toast.success('Saved!');

// With description
toast.success('Saved!', 'Your changes have been saved');
toast.error('Error', 'Something went wrong');
toast.info('Tip', 'Swipe to dismiss');

// Promise toast - shows loading, then success/error
toast.promise(fetchData(), {
  loading: { title: 'Loading...', description: 'Please wait' },
  success: { title: 'Done!', description: 'Data loaded' },
  error: (err) => ({ title: 'Failed', description: err.message }),
});

// Custom toast - fully custom content with animations
toast.custom(({ dismiss }) => (
  <View style={{ padding: 16, flexDirection: 'row', alignItems: 'center' }}>
    <Image source={{ uri: 'avatar.png' }} style={{ width: 40, height: 40 }} />
    <Text>New message from John</Text>
    <Button title="Reply" onPress={dismiss} />
  </View>
));

Customization

Per-Toast Options

Pass an options object as the second argument to customize individual toasts:

toast.success('Saved!', {
  description: 'Your changes have been saved',
  duration: 5000,
  icon: <CustomIcon />,
  style: { backgroundColor: '#fff' },
  dismissible: true,
  showCloseButton: true,
});

Custom Toasts

Create fully custom toasts where you control all the content. Your component fills the entire toast container and receives all entry/exit/stack animations automatically:

// Using a render function (recommended - gives access to dismiss)
toast.custom(({ dismiss, id, type, isExiting }) => (
  <View style={{ padding: 16, flexDirection: 'row', alignItems: 'center', gap: 12 }}>
    <Image source={{ uri: 'avatar.png' }} style={{ width: 44, height: 44, borderRadius: 22 }} />
    <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' }}>Reply</Text>
    </Pressable>
  </View>
));

// Or pass a React component directly
toast.custom(<MyNotificationCard />);

// With options
toast.custom(<MyToast />, {
  duration: 5000,
  dismissible: false,
  style: { backgroundColor: '#fef2f2' }
});

Global Configuration

Customize all toasts globally via the config prop on <BreadLoaf />:

<BreadLoaf
  config={{
    position: 'bottom',
    rtl: false, // Enable for RTL languages
    stacking: true,
    maxStack: 3,
    defaultDuration: 4000,
    colors: {
      success: { accent: '#22c55e', background: '#f0fdf4' },
      error: { accent: '#ef4444', background: '#fef2f2' },
    }
  }}
/>

Available options include:

  • position: 'top' | 'bottom' - Where toasts appear
  • offset: Extra spacing from screen edge
  • stacking: Show multiple toasts stacked
  • maxStack: Max visible toasts when stacking
  • dismissible: Allow swipe to dismiss
  • showCloseButton: Show X button
  • defaultDuration: Default display time in ms
  • colors: Custom colors per toast type
  • icons: Custom icons per toast type
  • toastStyle, titleStyle, descriptionStyle: Global style overrides

API Reference

Method Description
toast.success(title, description?) Show success toast
toast.error(title, description?) Show error toast
toast.info(title, description?) Show info toast
toast.promise(promise, messages) Show loading → success/error toast
toast.custom(content, options?) Show fully custom toast with your own content
toast.dismiss(id) Dismiss a specific toast
toast.dismissAll() Dismiss all toasts

Toasts in Modals

Toasts automatically appear above native modals on iOS.

On Android, you have two options:

Option 1: Use a Contained Modal

The simplest fix is to use containedModal presentation instead of modal. On Android, modal and containedModal look nearly identical, so this is an easy swap:

<Stack.Screen
  name="(modal)"
  options={{ presentation: Platform.OS === "android" ? "containedModal" : "modal" }}
/>

This renders the modal within the React hierarchy on Android, so toasts from your root <BreadLoaf /> remain visible.

Option 2: Use ToastPortal

If you need native modals, add <ToastPortal /> inside your modal layouts:

// app/(modal)/_layout.tsx
import { Stack } from "expo-router";
import { ToastPortal } from "react-native-bread";

export default function ModalLayout() {
  return (
    <>
      <Stack screenOptions={{ headerShown: false }} />
      <ToastPortal />
    </>
  );
}

The ToastPortal component only renders on Android - it returns null on iOS, so no platform check is needed.