rn-anywhere-toast
v0.1.0
Published
React Native Customizable Toasts That Works With Modals! Well Anywhere Really.
Maintainers
Readme
rn-anywhere-toast
A React Native / Expo toast package built for places where normal absolute overlays are not enough, especially React Native Modal.
React Native modals are rendered in a native layer above the main app UI. A root-level toast with a very high zIndex can still appear behind a modal. rn-anywhere-toast keeps a global showToast API, but lets you mount a ToastReceiver inside the active modal so the toast is rendered in that native layer.
Installation
npm install rn-anywhere-toast
npx expo install react-native-gesture-handler react-native-reanimated react-native-svg
npm install lucide-react-nativeMake sure Reanimated is configured in your app. Expo projects usually need the Reanimated Babel plugin:
// babel.config.js
module.exports = function (api) {
api.cache(true);
return {
presets: ['babel-preset-expo'],
plugins: ['react-native-reanimated/plugin'],
};
};Basic Usage
Wrap your app with ToastProvider.
import { GestureHandlerRootView } from 'react-native-gesture-handler';
import { ToastProvider } from 'rn-anywhere-toast';
export default function App() {
return (
<GestureHandlerRootView style={{ flex: 1 }}>
<ToastProvider>
<RootNavigator />
</ToastProvider>
</GestureHandlerRootView>
);
}Show a toast from any child component.
import { Button } from 'react-native';
import { useToast } from 'rn-anywhere-toast';
export function SaveButton() {
const { showToast } = useToast();
return (
<Button
title="Save"
onPress={() => {
showToast({
type: 'success',
message: 'Saved successfully',
});
}}
/>
);
}Built-in types:
'success' | 'error' | 'info' | 'warning'If no type is passed, info is used.
Toasts Above Modals
For normal screens, the provider renders the toast overlay for you. For React Native Modal, mount ToastReceiver inside the modal.
import { Modal, View } from 'react-native';
import { ToastReceiver } from 'rn-anywhere-toast';
export function EditModal({ visible }: { visible: boolean }) {
return (
<Modal visible={visible} animationType="slide">
<ToastReceiver />
<View>
<ModalContent />
</View>
</Modal>
);
}When the receiver is mounted, showToast sends new toasts to that receiver instead of the provider's root overlay. That keeps the toast above the modal on iOS and Android.
Receivers are stacked. If you open a modal on top of another modal, the most recently mounted ToastReceiver receives new toasts. When it unmounts, the previous receiver becomes active again.
Styling The Built-In Toasts
Use config to customize the built-in toast UI globally or per type.
import { ToastProvider } from 'rn-anywhere-toast';
export default function App() {
return (
<ToastProvider
config={{
duration: 4000,
topOffset: 64,
success: {
backgroundColor: '#E6F4EA',
textColor: '#1E4620',
iconColor: '#1E4620',
containerStyle: {
borderRadius: 12,
borderWidth: 1,
borderColor: '#1E4620',
},
textStyle: {
fontWeight: '600',
},
},
error: {
backgroundColor: '#FFDEDE',
textColor: '#5C1D1D',
hideIcon: true,
},
}}
>
<RootNavigator />
</ToastProvider>
);
}Custom Icons
Pass a React node or a render function.
<ToastProvider
config={{
warning: {
icon: <MyWarningIcon />,
},
success: {
icon: ({ dismiss }) => <MySuccessIcon onPress={dismiss} />,
},
}}
>
<RootNavigator />
</ToastProvider>Bring Your Own Toast Component
Use renderToast when you want full control. You can set it globally or per type.
import { Pressable, Text, View } from 'react-native';
import { ToastProvider } from 'rn-anywhere-toast';
export default function App() {
return (
<ToastProvider
config={{
renderToast: ({ message, type, dismiss }) => (
<Pressable onPress={dismiss}>
<View
style={{
width: 340,
padding: 16,
borderRadius: 14,
backgroundColor: type === 'error' ? '#2A1010' : '#111827',
}}
>
<Text style={{ color: '#FFFFFF', fontWeight: '700' }}>
{type.toUpperCase()}
</Text>
<Text style={{ color: '#FFFFFF', marginTop: 4 }}>{message}</Text>
</View>
</Pressable>
),
}}
>
<RootNavigator />
</ToastProvider>
);
}Per-type custom toasts override the global renderer:
<ToastProvider
config={{
error: {
renderToast: ({ message, dismiss }) => (
<ErrorToast message={message} onClose={dismiss} />
),
},
}}
>
<RootNavigator />
</ToastProvider>Per-Toast Options
showToast({
id: 'profile-saved',
type: 'success',
message: 'Profile saved',
duration: 5000,
data: {
source: 'settings-screen',
},
});Set duration: 0 to keep the toast visible until the user swipes it away or your custom component calls dismiss.
API
ToastProvider
<ToastProvider config={config}>{children}</ToastProvider>useToast
const { showToast } = useToast();showToast accepts:
type ShowToastOptions = {
id?: string;
message: React.ReactNode;
type?: 'success' | 'error' | 'info' | 'warning';
duration?: number;
data?: Record<string, unknown>;
};It returns the toast id.
ToastReceiver
Mount this inside a native layer, usually a React Native Modal, when toasts need to appear above that layer.
<Modal visible={visible}>
<ToastReceiver />
<ModalContent />
</Modal>ToastConfig
type ToastConfig = {
duration?: number;
topOffset?: number;
overlayStyle?: StyleProp<ViewStyle>;
toastWrapperStyle?: StyleProp<ViewStyle>;
renderToast?: (props: ToastRenderProps) => React.ReactNode;
success?: ToastStyleConfig;
error?: ToastStyleConfig;
info?: ToastStyleConfig;
warning?: ToastStyleConfig;
default?: ToastStyleConfig;
};type ToastStyleConfig = {
backgroundColor?: string;
textColor?: string;
iconColor?: string;
icon?: React.ReactNode | ((props: ToastRenderProps) => React.ReactNode);
hideIcon?: boolean;
duration?: number;
containerStyle?: StyleProp<ViewStyle>;
textStyle?: StyleProp<TextStyle>;
renderToast?: (props: ToastRenderProps) => React.ReactNode;
};type ToastRenderProps = {
toast: Toast;
message: React.ReactNode;
type: 'success' | 'error' | 'info' | 'warning';
dismiss: () => void;
config?: ToastConfig;
typeConfig?: ToastStyleConfig;
};Development
npm install
npm run typecheck
npm run build