react-native-sonner
v0.2.0
Published
An opinionated toast component for React Native.
Maintainers
Readme
React Native Sonner
An opinionated toast component for React Native inspired by Sonner.
Features
- Minimalist design - Clean, modern toast notifications
- Smooth animations - Spring and timing animations powered by Reanimated
- Swipe to dismiss - Native gesture support with elastic resistance
- Dark mode - Automatic theme detection
- Cross-platform - iOS, Android, and Expo
- Simple API - Just
toast("Hello!")anywhere - Promise toasts - Loading, success, and error states
- Rich colors - Optional vibrant backgrounds
- Multi-toast support - Queue management with visible limit
- Haptic feedback - Optional haptics for toast events
- Accessibility - Screen reader announcements and labels
- Performant - 60fps animations with worklet-based gestures
- Highly customizable - Styles, icons, and animations
Installation
npm install react-native-sonnerPeer Dependencies
npm install react-native-reanimated react-native-gesture-handler react-native-safe-area-context
# Optional: for SVG icons
npm install react-native-svg
# Optional: for haptic feedback
npm install expo-hapticsMake sure to follow the installation instructions for each peer dependency:
Quick Start
1. Add the Toaster component
import { Toaster } from 'react-native-sonner';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
import { SafeAreaProvider } from 'react-native-safe-area-context';
export default function App() {
return (
<GestureHandlerRootView style={{ flex: 1 }}>
<SafeAreaProvider>
<YourApp />
<Toaster />
</SafeAreaProvider>
</GestureHandlerRootView>
);
}2. Show toasts anywhere
import { toast } from 'react-native-sonner';
function MyComponent() {
return (
<Button
title="Show Toast"
onPress={() => toast('Hello World!')}
/>
);
}API
Basic Usage
// Default toast
toast('Hello World!');
// With description
toast('Event created', {
description: 'Your event has been scheduled',
});Toast Variants
toast.success('Success!');
toast.error('Something went wrong');
toast.warning('Please check your input');
toast.info('New update available');
toast.loading('Uploading...');Promise Toast
toast.promise(
fetchData(),
{
loading: 'Loading data...',
success: (data) => `Loaded ${data.count} items`,
error: (err) => `Error: ${err.message}`,
}
);Update Toast
const toastId = toast.loading('Uploading...');
// Update the toast later
toast.update(toastId, {
type: 'success',
title: 'Upload complete!',
});With Actions
toast('File deleted', {
action: {
label: 'Undo',
onClick: () => restoreFile(),
},
cancel: {
label: 'Dismiss',
onClick: () => {},
},
});Custom Duration
// 10 seconds
toast('Long toast', { duration: 10000 });
// Never auto-dismiss
toast('Sticky toast', { duration: Infinity });Important Toasts
Important toasts won't be hidden when the visible toast limit is reached:
toast.error('Critical error!', { important: true });Dismiss Programmatically
const toastId = toast('Loading...');
// Dismiss specific toast
toast.dismiss(toastId);
// Dismiss all toasts
toast.dismiss();Callbacks
toast('Hello', {
onDismiss: (t) => console.log('Toast dismissed:', t.id),
onAutoClose: (t) => console.log('Toast auto-closed:', t.id),
});Toaster Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| position | Position | "top-center" | Toast position on screen |
| theme | "light" \| "dark" \| "system" | "system" | Color theme |
| duration | number | 4000 | Default duration in ms |
| visibleToasts | number | 3 | Max visible toasts |
| gap | number | 12 | Gap between toasts |
| offset | object | { top: 52, bottom: 52, left: 16, right: 16 } | Screen edge offsets |
| swipeToDismiss | boolean | true | Enable swipe gestures |
| swipeDirection | SwipeDirection \| SwipeDirection[] | ["left", "right"] | Swipe directions |
| pauseOnAppBackground | boolean | true | Pause timer when app backgrounds |
| richColors | boolean | false | Vibrant backgrounds |
| closeButton | boolean | false | Show close button |
| hapticFeedback | boolean | false | Enable haptic feedback |
| icons | ToastIcons | - | Custom icons |
| toastStyles | ToastStyles | - | Default styles for all toasts |
| variantStyles | VariantStyles | - | Per-variant style overrides |
| containerStyle | ViewStyle | - | Container style |
| animation | AnimationConfig | - | Animation configuration |
| toasterId | string | - | ID for multiple Toaster instances |
Position Options
"top-left"/"top-center"/"top-right""bottom-left"/"bottom-center"/"bottom-right"
Animation Configuration
<Toaster
animation={{
duration: 350, // Entry animation duration (ms)
exitDuration: 200, // Exit animation duration (ms)
useSpring: true, // Use spring animation for entry
damping: 18, // Spring damping
stiffness: 140, // Spring stiffness
mass: 1, // Spring mass
}}
/>Per-toast animation:
toast('Custom animation', {
animation: {
duration: 500,
useSpring: false,
},
});Haptic Feedback
Enable haptic feedback for toast events (requires expo-haptics):
<Toaster hapticFeedback />Haptics are triggered on:
- Toast appearance (light impact, or notification type for success/warning/error)
- Action button press (medium impact)
- Cancel button press (light impact)
Accessibility
Toasts automatically announce to screen readers. Customize accessibility:
toast('Order placed', {
accessibility: {
announceToScreenReader: true, // default: true
accessibilityLabel: 'Your order has been successfully placed',
},
});Styling
Custom Toast Styles
toast('Styled toast', {
styles: {
container: { backgroundColor: '#1a1a1a' },
title: { color: '#fff', fontWeight: 'bold' },
description: { color: '#888' },
},
});Global Styles via Toaster
<Toaster
toastStyles={{
container: { borderRadius: 20 },
title: { fontFamily: 'Inter-Medium' },
}}
/>Per-Variant Styles
Style specific toast variants differently:
<Toaster
variantStyles={{
success: {
container: { borderLeftWidth: 4, borderLeftColor: '#22c55e' },
title: { fontWeight: '600' },
},
error: {
container: { borderLeftWidth: 4, borderLeftColor: '#ef4444' },
},
warning: {
container: { backgroundColor: '#fef3c7' },
},
info: {
title: { color: '#1e40af' },
},
// 'loading' and 'default' variants also available
}}
/>Style priority (lowest to highest):
- Base styles (theme defaults)
toastStyles(applies to all toasts)variantStyles[type](per-variant overrides)toast.styles(individual toast override)
Available Style Keys
container- Toast containercontent- Content wrappertitle- Title textdescription- Description textactionButton- Action buttonactionButtonText- Action button textcancelButton- Cancel buttoncancelButtonText- Cancel button textcloseButton- Close button
Custom Icons
<Toaster
icons={{
success: <MySuccessIcon />,
error: <MyErrorIcon />,
warning: <MyWarningIcon />,
info: <MyInfoIcon />,
loading: <MySpinner />,
}}
/>Per-toast icon:
toast('Custom icon', {
icon: <MyIcon />,
});Multiple Toaster Instances
Use toasterId to target specific Toaster instances:
// In your layout
<Toaster toasterId="main" position="top-center" />
<Toaster toasterId="bottom" position="bottom-center" />
// Show toast in specific toaster
toast('Top notification', { toasterId: 'main' });
toast('Bottom notification', { toasterId: 'bottom' });Hooks
useToastState
Access toast state in components:
import { useToastState } from 'react-native-sonner';
function ToastCounter() {
const { toasts } = useToastState();
return <Text>Active toasts: {toasts.length}</Text>;
}Expo Support
React Native Sonner works with Expo out of the box:
npx expo install react-native-reanimated react-native-gesture-handler react-native-safe-area-context react-native-svg expo-hapticsTypeScript
Full TypeScript support with exported types:
import type {
ToastT,
ToastType,
Position,
ToasterProps,
AnimationConfig,
ToastStyles,
ToastIcons,
VariantStyles,
} from 'react-native-sonner';License
MIT
