Skip to content

AlshehriAli0/react-native-bread

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

68 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

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+
  • 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 - code-level RTL for when you're not using native RTL (I18nManager)
  • Completely customizable - colors, icons, styles, animations
  • Full Expo compatibility
  • React Compiler compatible - all components are written to be optimized by react compiler

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,
  deduplication: false, // Opt out of deduplication for this toast
});

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, // Code-level RTL — not needed if using native RTL (I18nManager)
    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
  • deduplication: Prevent duplicate toasts (default: true, see below)
  • colors: Custom colors per toast type
  • icons: Custom icons per toast type
  • toastStyle, titleStyle, descriptionStyle: Global style overrides

Deduplication

Deduplication is enabled by default. When the same toast is shown repeatedly (e.g., rapid button taps), it prevents stacking identical toasts. Instead, it resets the timer and plays a feedback animation:

  • Non-error toasts: subtle pulse (scale bump)
  • Error toasts: shake effect

Disable globally:

<BreadLoaf config={{ deduplication: false }} />

Or per-toast (overrides global config):

// Opt out for a specific toast
toast.info('New message', { deduplication: false });

// Explicitly enable for a specific toast (redundant when global is on)
toast.success('Liked!', { deduplication: true });

By default, a toast is considered a duplicate when it matches the front toast by title, type, and description. For stable matching across different content, provide an id — the existing toast's content will be updated:

toast.success('Saved item 1', { deduplication: true, id: 'save-action' });
toast.success('Saved item 2', { deduplication: true, id: 'save-action' }); // updates content, resets timer

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.