React Native Inbox SDK
Native React Native components for a fully-featured notification inbox with real-time updates, preference management, and built-in light/dark theme support.
Looking for the web version? See React Inbox for React DOM components with CSS styling.
Installation
npm install @zyphr-dev/inbox-react-native
# or
yarn add @zyphr-dev/inbox-react-native
Peer dependencies: react >= 17.0.0, react-native >= 0.64.0
Quick Start
1. Generate a Subscriber Token (Server-Side)
The subscriber token is a JWT generated on your backend using your API key. Never expose your API key to the client.
app.get('/api/inbox-token', async (req, res) => {
const response = await fetch('https://api.zyphr.dev/v1/subscriber-inbox/token', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.ZYPHR_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ external_id: req.user.id }),
});
const { data } = await response.json();
res.json({ token: data.token });
});
2. Add the Provider and Components
import { ZyphrProvider, InboxPopover } from '@zyphr-dev/inbox-react-native';
function App() {
const [token, setToken] = useState('');
useEffect(() => {
fetch('/api/inbox-token')
.then(r => r.json())
.then(d => setToken(d.token));
}, []);
if (!token) return null;
return (
<ZyphrProvider subscriberToken={token}>
<Navigation />
</ZyphrProvider>
);
}
No CSS import needed -- styling is handled via StyleSheet and the theme context.
Provider Configuration
<ZyphrProvider
subscriberToken={token} // Required
apiUrl="https://api.zyphr.dev" // Default
wsUrl="wss://ws.api.zyphr.dev" // Default
realtime={true} // Enable WebSocket updates (default: true)
theme="system" // 'light' | 'dark' | 'system' (default: 'system')
locale="en" // Locale for date formatting (default: 'en')
demo={false} // Demo mode with mock data (default: false)
>
{children}
</ZyphrProvider>
| Prop | Type | Default | Description |
|---|---|---|---|
subscriberToken | string | -- | Required. Subscriber JWT from /v1/subscriber-inbox/token |
apiUrl | string | https://api.zyphr.dev | API endpoint |
wsUrl | string | wss://ws.api.zyphr.dev | WebSocket endpoint |
realtime | boolean | true | Enable real-time WebSocket updates |
theme | 'light' | 'dark' | 'system' | 'system' | Color scheme (uses RN Appearance API) |
locale | string | 'en' | Locale for date formatting |
demo | boolean | false | Render with mock data, no API calls |
Unlike the web version, 'system' theme uses React Native's Appearance API, which works natively on both iOS and Android without DOM access.
Components
InboxBell
Bell icon button with unread count badge.
import { InboxBell } from '@zyphr-dev/inbox-react-native';
<InboxBell onPress={() => navigation.navigate('Inbox')} />
<InboxBell dot />
<InboxBell size={48} maxCount={50} />
| Prop | Type | Default | Description |
|---|---|---|---|
onPress | () => void | -- | Press handler |
style | ViewStyle | -- | Container style |
dot | boolean | false | Show dot instead of count |
maxCount | number | 99 | Maximum display count (e.g., 99+) |
bellIcon | ReactNode | -- | Custom bell icon element |
size | number | 40 | Button size in points |
InboxPopover
Modal-based notification feed triggered by clicking the bell.
import { InboxPopover } from '@zyphr-dev/inbox-react-native';
// Uncontrolled (manages own visibility)
<InboxPopover />
// Controlled
<InboxPopover
visible={isOpen}
onVisibilityChange={setIsOpen}
animationType="fade"
/>
// Custom trigger
<InboxPopover trigger={<MyCustomButton />} />
| Prop | Type | Default | Description |
|---|---|---|---|
visible | boolean | -- | Controlled visibility |
onVisibilityChange | (visible: boolean) => void | -- | Visibility callback |
style | ViewStyle | -- | Modal content style |
categories | string[] | -- | Filter by categories |
trigger | ReactNode | -- | Custom trigger element |
animationType | 'slide' | 'fade' | 'none' | 'slide' | Modal animation |
presentationStyle | string | 'pageSheet' | iOS modal presentation style |
InboxFeed
Full notification list with All/Unread tabs, pull-to-refresh, and infinite scroll via FlatList.
import { InboxFeed } from '@zyphr-dev/inbox-react-native';
<InboxFeed />
<InboxFeed categories={['alerts', 'updates']} />
| Prop | Type | Default | Description |
|---|---|---|---|
categories | string[] | -- | Filter by categories |
style | ViewStyle | -- | Container style |
showArchived | boolean | false | Include archived notifications |
emptyState | ReactNode | -- | Custom empty state component |
renderItem | (notification, actions) => ReactNode | -- | Custom notification renderer |
NotificationItem
Renders a single notification with priority styling, relative timestamps, and action buttons.
import { NotificationItem } from '@zyphr-dev/inbox-react-native';
<NotificationItem
notification={notification}
onPress={() => handlePress(notification)}
/>
PreferencesPanel
ScrollView-based notification preference management with native Switch toggles.
import { PreferencesPanel } from '@zyphr-dev/inbox-react-native';
<PreferencesPanel
onSave={() => Alert.alert('Saved!')}
onError={(err) => Alert.alert('Error', err.message)}
/>
| Prop | Type | Default | Description |
|---|---|---|---|
style | ViewStyle | -- | Container style |
showGlobalPreferences | boolean | true | Show global preferences section |
channelLabels | Record<string, string> | -- | Custom channel labels |
onSave | () => void | -- | Save success callback |
onError | (error: Error) => void | -- | Error callback |
saveButtonText | string | 'Save Preferences' | Custom button text |
channels | NotificationChannel[] | -- | Filter to specific channels |
Hooks
All hooks share the same API as the web version -- only the import path changes.
useInbox
import { useInbox } from '@zyphr-dev/inbox-react-native';
function NotificationScreen() {
const {
notifications, unreadCount, isLoading, hasMore, error,
markAsRead, markAllAsRead, archive, delete: remove, loadMore, refresh,
} = useInbox();
return (
<FlatList
data={notifications}
renderItem={({ item }) => (
<Pressable onPress={() => markAsRead(item.id)}>
<Text style={{ fontWeight: item.read_at ? 'normal' : 'bold' }}>
{item.title}
</Text>
</Pressable>
)}
onEndReached={() => hasMore && loadMore()}
onRefresh={refresh}
refreshing={isLoading}
/>
);
}
useUnreadCount
import { useUnreadCount } from '@zyphr-dev/inbox-react-native';
function TabBadge() {
const { count } = useUnreadCount();
if (count === 0) return null;
return <View style={styles.badge}><Text>{count}</Text></View>;
}
useRealTime
import { useRealTime } from '@zyphr-dev/inbox-react-native';
function ConnectionIndicator() {
const { connected } = useRealTime();
return (
<View style={{
backgroundColor: connected ? '#22c55e' : '#ef4444',
width: 8, height: 8, borderRadius: 4,
}} />
);
}
usePreferences
import { usePreferences } from '@zyphr-dev/inbox-react-native';
function CustomPreferences() {
const { preferences, updatePreference, savePreferences, hasChanges } = usePreferences();
// Build your own preference UI using the data
}
Theming
Using the Theme Hook
Access the current theme anywhere inside the provider:
import { useZyphrTheme } from '@zyphr-dev/inbox-react-native';
function CustomComponent() {
const theme = useZyphrTheme();
return (
<View style={{ backgroundColor: theme.colors.background, padding: theme.spacing.lg }}>
<Text style={{ color: theme.colors.text, fontSize: theme.fontSize.md }}>
Styled with Zyphr theme
</Text>
</View>
);
}
Theme Structure
interface ZyphrTheme {
colors: {
primary, primaryHover, primarySubtle,
background, backgroundSecondary, backgroundHover,
text, textSecondary, textMuted,
border, borderSubtle,
danger, dangerHover, success, warning, urgent, unreadDot,
skeletonBase, skeletonShine,
};
spacing: { xs: 4, sm: 8, md: 12, lg: 16, xl: 24 };
borderRadius: { sm: 6, md: 10, lg: 14 };
fontSize: { xs: 11, sm: 13, md: 15, lg: 17, xl: 20 };
}
Accessing Color Palettes
import { LIGHT_COLORS, DARK_COLORS } from '@zyphr-dev/inbox-react-native';
Comparison with Web Version
| Feature | Web (@zyphr-dev/inbox-react) | React Native (@zyphr-dev/inbox-react-native) |
|---|---|---|
| Styling | CSS + CSS variables | StyleSheet + theme context |
| Popover | Positioned div + backdrop | Modal component |
| Infinite scroll | IntersectionObserver | FlatList.onEndReached |
| Pull to refresh | Not built-in | FlatList.onRefresh |
| Links | window.open() | Linking.openURL() |
| Theme detection | window.matchMedia() | Appearance API |
| Toggles | Custom CSS checkbox | Native Switch |
| Focus trap | DOM focus management | Native modal handles it |
| Icons | Inline SVG | Unicode (customizable via props) |
| CSS import | Required (styles.css) | Not needed |
TypeScript
Full TypeScript support with exported types:
import type {
Notification,
ZyphrConfig,
ZyphrTheme,
ZyphrThemeColors,
UseInboxReturn,
UseUnreadCountReturn,
UseRealTimeReturn,
UsePreferencesReturn,
} from '@zyphr-dev/inbox-react-native';
Requirements
- React Native 0.64+ (for
Intl.RelativeTimeFormatsupport) - Expo SDK 52+ (if using Expo)
- iOS 13+ / Android API 24+