Flashbar notifications have become an essential UI pattern in modern mobile applications, providing users with immediate feedback about app operations, errors, and important information without disrupting their workflow. This guide walks you through creating a fully-featured flashbar component from scratch.
What You'll Learn
- How to implement smooth animations using React Native's Animated API
- Building a notification queue system for managing multiple notifications
- Supporting different notification types with visual differentiation
- Implementing accessible, dismissible notifications with action buttons
- Creating a context-based API for global notification access
What Is a Flashbar Component
A flashbar is an animated notification banner that appears at the top or bottom of the screen to convey information to users. The term "flashbar" combines "flash" (indicating its transient nature) with "bar" (describing its visual form as a horizontal banner). Unlike modal dialogs that require user interaction, flashbars typically dismiss automatically after a set duration, though they may include action buttons for user engagement.
Use Cases for Flashbars
- Success Confirmations: Notify users when actions complete successfully
- Error Alerts: Communicate failures or problems that need attention
- Status Updates: Show progress on background operations
- Contextual Information: Display relevant tips or information
Flashbar vs Snackbar
While both serve similar purposes, snackbars typically appear at the bottom and may include a single action button. Flashbars traditionally appear at the top and often support multiple notifications displayed in sequence or stacked. In React Native implementations, these distinctions often blur, and many developers create hybrid components that can be positioned flexibly while supporting various interaction patterns.
The choice between using a flashbar or snackbar often depends on the context and importance of the notification. Use flashbars for general status updates, confirmations, and informational messages that don't require immediate action. Use snackbars when users might need to respond to the notification, such as undoing an action or expanding for more details. Many modern applications implement both patterns and choose between them based on the specific notification context. For example, a social media app might use a flashbar to confirm a post was saved while using a snackbar to offer an "Undo" option after deleting content.
Modern applications use flashbars for a wide range of scenarios. E-commerce apps display them to confirm orders and communicate shipping updates. Social media applications use flashbars to notify users of new messages or interactions. Productivity tools leverage flashbars to show task completions and deadline reminders. Financial applications employ flashbars to confirm transactions and alert users to account activity. The pattern's versatility makes it a fundamental building block for any React Native application that needs to communicate with users effectively.
Setting Up Your React Native Project
Before implementing a flashbar component, ensure your React Native development environment is properly configured. This guide assumes you have a working React Native project set up with either Expo or a bare React Native CLI project. The implementation uses React Native's built-in Animated API, which works identically in both environments without additional dependencies.
Begin by creating a new component file for your flashbar implementation. A well-organized project structure keeps related components together, so consider creating a notifications or components directory within your source structure. The flashbar implementation will consist of multiple parts: the flashbar component itself, a context provider for managing notification state, and TypeScript interfaces if you're using TypeScript.
Project Structure
Create a well-organized structure for your flashbar components:
src/
components/
Flashbar/
index.tsx // Main component and exports
FlashbarView.tsx // Animated container component
types.ts // TypeScript interfaces and types
constants.ts // Animation timing and style constants
hooks/
useFlashbar.ts // Context hook for components
context/
FlashbarContext.tsx // React Context provider
FlashbarProvider.tsx // Provider implementation
Your project should include React Native's core dependencies for animation and gesture handling:
// Core dependencies for flashbar implementation
import {
Animated,
Dimensions,
Easing,
Platform,
StyleSheet,
AccessibilityInfo,
} from 'react-native';
The Animated API provides the foundation for smooth flashbar animations, while the Dimensions API helps with responsive positioning. Platform-specific handling ensures your flashbar works correctly on both iOS and Android, as each platform has subtle differences in how it handles keyboard events and safe areas. The AccessibilityInfo API enables you to respect user preferences for reduced motion and other accessibility settings.
Consider adding TypeScript to your project for better development experience and type safety. TypeScript interfaces for flashbar props, notification types, and configuration objects help catch errors during development and serve as documentation for your component's API. Even if your project primarily uses JavaScript, the type definitions you create will clarify your component's expected inputs and behavior. For teams working on custom web applications, investing in TypeScript pays dividends in maintainability and code quality.
Implementing the Animated Container
The flashbar's visual appearance relies on Animated.View, which provides hardware-accelerated animations for smooth transitions. Creating the animated container requires understanding how Animated values work with layout properties to create the characteristic slide-in and slide-out effects that flashbars are known for.
Entry and Exit Animations
const AnimatedFlashbar: React.FC<FlashbarProps> = ({ notification, onDismiss }) => {
const translateY = useRef(new Animated.Value(-100));
const opacity = useRef(new Animated.Value(0));
useEffect(() => {
// Entry animation - slide in from top with fade
Animated.parallel([
Animated.timing(translateY.current, {
toValue: 0,
duration: 300,
easing: Easing.out(Easing.cubic),
useNativeDriver: true,
}),
Animated.timing(opacity.current, {
toValue: 1,
duration: 300,
useNativeDriver: true,
}),
]).start();
}, []);
const handleDismiss = () => {
// Exit animation - slide out with fade
Animated.parallel([
Animated.timing(translateY.current, {
toValue: -100,
duration: 250,
easing: Easing.in(Easing.cubic),
useNativeDriver: true,
}),
Animated.timing(opacity.current, {
toValue: 0,
duration: 250,
useNativeDriver: true,
}),
]).start(() => onDismiss());
};
return (
<Animated.View
style={[
styles.container,
{
transform: [{ translateY: translateY.current }],
opacity: opacity.current,
},
]}
>
{/* Flashbar content */}
</Animated.View>
);
};
Animation Timing and Easing
Configuring animation timing appropriately significantly impacts user perception of your flashbar. Too fast, and users might miss the notification entirely. Too slow, and the flashbar feels sluggish and intrusive. The optimal duration depends on the notification's importance and length, but most implementations use durations between 2,000 and 5,000 milliseconds for display time, with 300ms entry and 250ms exit animations.
Easing functions control how the animation accelerates and decelerates, adding polish to the motion. The entrance animation typically uses an easing function that starts quickly and settles gradually, drawing attention immediately while avoiding harsh movements. Easing.out(Easing.cubic) provides a natural deceleration that feels smooth and professional. Exit animations often use Easing.in(Easing.cubic) to create a distinct visual separation between the entry and exit phases.
Staggering animations when displaying multiple notifications requires careful timing coordination. Each notification should wait for the previous notification's exit animation to complete before beginning its entry. This creates a natural queue behavior where notifications appear one after another rather than simultaneously. The total time for each notification includes its display duration plus both entry and exit animation times.
Animation interpolation allows you to create more sophisticated effects by mapping animated values to different property changes. For example, you might interpolate the opacity to fade the flashbar in while simultaneously translating it into position, creating a more polished visual effect than either animation alone would provide. Using useNativeDriver: true ensures animations run on the UI thread without causing janky performance on the JavaScript thread.
1interface AnimationConfig {2 entryDuration: number; // Default: 300ms for entry animation3 exitDuration: number; // Default: 250ms for exit animation4 easing: EasingFunction; // Default: Easing.out(Easing.cubic)5 exitEasing: EasingFunction; // Default: Easing.in(Easing.cubic)6 staggerDelay: number; // Delay between queue items (100ms)7}8 9const DEFAULT_ANIMATION_CONFIG: AnimationConfig = {10 entryDuration: 300,11 exitDuration: 250,12 easing: Easing.out(Easing.cubic),13 exitEasing: Easing.in(Easing.cubic),14 staggerDelay: 100,15};16 17// Position configurations for top/bottom placement18const POSITION_OFFSETS = {19 top: -100, // Start from above viewport20 bottom: 100, // Start from below viewport21};Building the Notification Queue
A robust flashbar implementation needs to handle multiple notifications that may arrive while previous notifications are still displaying. The notification queue manages this flow, ensuring each notification gets displayed in order and preventing notifications from being lost or overlapped inappropriately.
The queue maintains a list of pending notifications and tracks the currently displaying notification. When a new notification arrives, it's added to the queue if another notification is currently displaying. When the current notification completes, the queue automatically advances to display the next pending notification. This behavior ensures that rapid-fire notifications don't create confusing overlapping displays.
Queue Implementation
interface NotificationQueueState {
queue: Notification[];
currentNotification: Notification | null;
isVisible: boolean;
}
type QueueAction =
| { type: 'ENQUEUE'; payload: Notification }
| { type: 'DISMISS' }
| { type: 'DISPLAY'; payload: Notification }
| { type: 'CLEAR' };
const notificationReducer = (
state: NotificationQueueState,
action: QueueAction
): NotificationQueueState => {
switch (action.type) {
case 'ENQUEUE':
return {
...state,
queue: [...state.queue, action.payload],
};
case 'DISPLAY':
return {
...state,
currentNotification: action.payload,
isVisible: true,
queue: state.queue.filter(n => n.id !== action.payload.id),
};
case 'DISMISS':
return {
...state,
isVisible: false,
};
case 'CLEAR':
return {
queue: [],
currentNotification: null,
isVisible: false,
};
default:
return state;
}
};
const [state, dispatch] = useReducer(notificationReducer, {
queue: [],
currentNotification: null,
isVisible: false,
});
Queue Auto-Advance Logic
The queue automatically advances to display the next notification when the current notification completes. This requires careful coordination between animation completion and state updates:
const showNotification = (notification: Notification) => {
dispatch({ type: 'ENQUEUE', payload: notification });
};
// Auto-advance queue when notification completes
useEffect(() => {
if (!state.currentNotification && state.queue.length > 0) {
const next = state.queue[0];
dispatch({ type: 'DISPLAY', payload: next });
}
}, [state.queue, state.currentNotification]);
Handling rapid notifications requires special consideration to prevent queue buildup that could delay important messages indefinitely. One approach implements a maximum queue size, discarding older notifications when the queue exceeds a certain length. Another approach varies display duration based on notification priority, ensuring critical messages appear sooner than routine notifications. The queue also needs to handle edge cases like removing specific notifications from the queue before they display.
Different notification types serve different purposes and should be visually distinguishable.
Success
Confirm completed actions with green backgrounds and checkmark icons.
Error
Alert users to problems with red backgrounds and warning icons.
Warning
Caution about potential issues with yellow/orange backgrounds.
Info
Provide neutral information with blue or gray backgrounds.
Implementing User Actions
Flashbar components often include action buttons that allow users to respond to notifications without navigating away from their current context. These actions might include dismissing the notification, undoing an action, viewing more details, or triggering a related action. Designing action placement and behavior requires balancing quick access with avoiding accidental triggers.
Action Button Implementation
interface FlashbarAction {
text: string;
onPress: () => void;
dismissAfterPress?: boolean;
}
const FlashbarWithActions: React.FC<FlashbarProps> = ({
notification,
onDismiss
}) => {
const handleAction = (action: FlashbarAction) => {
action.onPress();
if (action.dismissAfterPress) {
onDismiss();
}
};
return (
<View style={styles.container}>
<Text style={styles.message}>{notification.message}</Text>
{notification.actions && (
<View style={styles.actionsContainer}>
{notification.actions.map((action, index) => (
<TouchableOpacity
key={index}
style={styles.actionButton}
onPress={() => handleAction(action)}
accessibilityLabel={action.text}
>
<Text style={styles.actionText}>{action.text}</Text>
</TouchableOpacity>
))}
</View>
)}
</View>
);
};
The dismiss action allows users to remove the notification immediately if they don't need to see it for its full duration. This action typically appears as a close button (X icon) in the flashbar's corner, consistent with common mobile design patterns. Implementing dismiss requires stopping any pending auto-dismiss timer and triggering the exit animation immediately.
Accessibility for Actions
- Use accessibilityLabel for screen reader users
- Ensure minimum touch target size (44x44 points on iOS, 48dp on Android)
- Provide clear, action-oriented button text
- Add accessibilityHint to describe what happens when the action is triggered
Positioning action buttons within the flashbar affects both aesthetics and usability. Placing the dismiss button in a corner and primary actions along the bottom or right edge follows common patterns that users expect. The flashbar's height should accommodate the action buttons without crowding the message text, or the message should truncate appropriately when action buttons are present.
Accessibility Considerations
Accessible flashbar implementation ensures all users can perceive and interact with notifications effectively. This involves proper labeling for screen readers, sufficient color contrast for visual perception, and appropriate timing for users who need more time to read the notification content.
Screen Reader Support
<Animated.View
style={[styles.container, typeStyles[notification.type]]}
accessibilityLabel={`${notification.type}: ${notification.message}`}
accessibilityRole="alert"
accessibilityHint={notification.actions?.length
? `Actions available: ${notification.actions.map(a => a.text).join(', ')}`
: 'Notification will dismiss automatically'}
accessibilityLiveRegion="polite"
>
{/* Flashbar content */}
</Animated.View>
Screen reader accessibility begins with the accessibilityLabel property, which should convey the notification's content and type. A well-labeled flashbar might announce "Success: Your changes have been saved" or "Error: Unable to connect to the server." This information helps screen reader users understand the notification without needing to navigate to it specifically.
Color Contrast Requirements
Color contrast requirements ensure that notification text remains readable regardless of the background color chosen for each notification type. The Web Content Accessibility Guidelines (WCAG) specify minimum contrast ratios for text against backgrounds, with 4.5:1 being the standard for normal text. Testing your flashbar's color combinations helps identify any accessibility issues before they affect users.
Extended display duration addresses users who need more time to read and process notification content. While the standard 3-5 second duration works for many users, some may need 10 seconds or more. Implementing an option to increase display duration ensures your flashbar works for users with cognitive disabilities or those reading in a non-native language.
Integrating with React Context
Creating a global notification system through React Context allows any component in your application to trigger flashbar notifications without prop drilling or tight coupling. The context provider wraps your application and manages the notification queue, making the flashbar available through a simple hook while keeping the implementation details encapsulated.
Context Provider Implementation
const FlashbarContext = createContext<FlashbarApi | null>(null);
export const FlashbarProvider: React.FC<{ children: ReactNode }> = ({
children
}) => {
const [notifications, setNotifications] = useState<Notification[]>([]);
const [currentNotification, setCurrentNotification] = useState<Notification | null>(null);
const showNotification = useCallback((notification: Notification) => {
setNotifications(prev => [...prev, notification]);
}, []);
const hideNotification = useCallback((id: string) => {
setNotifications(prev => prev.filter(n => n.id !== id));
setCurrentNotification(prev => prev?.id === id ? null : prev);
}, []);
return (
<FlashbarContext.Provider value={{ showNotification, hideNotification }}>
{children}
<FlashbarComponent
notifications={notifications}
currentNotification={currentNotification}
onDismiss={hideNotification}
/>
</FlashbarContext.Provider>
);
};
// Hook for consuming the context
const useFlashbar = () => {
const context = useContext(FlashbarContext);
if (!context) {
throw new Error('useFlashbar must be used within FlashbarProvider');
}
return context;
};
// Usage in any component
const SettingsScreen = () => {
const { showNotification } = useFlashbar();
const handleSave = async () => {
await saveSettings();
showNotification({
id: Date.now().toString(),
type: 'success',
message: 'Settings saved successfully',
duration: 3000,
});
};
return <Button title="Save" onPress={handleSave} />;
};
The context API consists of a provider component that manages state and exposes methods for displaying notifications. Components throughout your application consume this context using the useContext hook, calling methods like showNotification or hideNotification to interact with the flashbar. This pattern keeps notification logic centralized while making it universally accessible.
Type safety in the context implementation requires careful attention to the types exposed through the context value. The notification display method should accept a well-defined notification object with required and optional properties. This contract ensures that components throughout your application provide the necessary information while allowing flexibility in optional fields.
Testing Your Flashbar Implementation
Comprehensive testing ensures your flashbar works correctly across different scenarios and devices. Test coverage should include rendering, animations, queue behavior, user interactions, and edge cases like rapid notifications or empty queues.
Test Coverage Areas
- Rendering: Verify correct display with different notification types
- Animations: Test entry/exit animations and timing
- Queue Behavior: Verify notification ordering and auto-advance
- User Interactions: Test dismiss and action button functionality
- Accessibility: Verify screen reader announcements
// Example test case for notification type styling
it('displays notification with correct type styling', async () => {
const errorNotification: Notification = {
id: '1',
type: 'error',
message: 'Connection failed',
};
render(<Flashbar notification={errorNotification} />);
await waitFor(() => {
const flashbar = screen.getByTestId('flashbar-container');
expect(flashbar.props.style).toContainEqual(
expect.objectContaining({ backgroundColor: COLORS.error })
);
});
});
// Test queue behavior
it('displays queued notifications in order', async () => {
const { showNotification } = renderFlashbarWithContext();
showNotification({ id: '1', message: 'First', type: 'info' });
showNotification({ id: '2', message: 'Second', type: 'success' });
await waitFor(() => {
expect(screen.getByText('First')).toBeTruthy();
});
// Advance timer and verify second notification
jest.advanceTimersByTime(4000);
await waitFor(() => {
expect(screen.getByText('Second')).toBeTruthy();
});
});
Snapshot testing provides a baseline for visual regression testing, capturing the flashbar's rendered output at a point in time. When combined with a tool like Jest's snapshot testing, you can detect unintended changes to the flashbar's structure or styling. Be aware that snapshot tests require updates when intentionally changing the component's output.
Integration testing verifies that components throughout your application can successfully trigger notifications through the context API. These tests should simulate user actions that generate notifications and verify that the flashbar appears with the expected content and behavior. Testing the full flow from notification trigger through dismissal ensures your implementation works end-to-end.
Animation testing poses unique challenges because animations involve time-dependent state changes. Testing libraries like React Native Testing Library provide utilities for waiting for animations to complete before making assertions. Testing animation timing helps ensure the flashbar displays for the expected duration before auto-dismissing.
Performance Optimization
Flashbar components can impact application performance if not implemented carefully, especially when handling frequent notifications or animating complex layouts. Understanding performance bottlenecks and optimization strategies helps maintain smooth application performance while delivering good user experience. For enterprise web applications, proper performance optimization is essential for maintaining user satisfaction across all devices.
Memoization Strategies
// Memoize the flashbar component to prevent unnecessary re-renders
export const Flashbar = React.memo<FlashbarProps>(
({ notification, onDismiss },
previousProps) => {
// Custom comparison function
const shouldUpdate = (
prev.notification?.id !== notification?.id ||
prev.notification?.message !== notification?.message
);
return !shouldUpdate;
}
);
// Memoize callback functions in the provider
export const FlashbarProvider: React.FC<ProviderProps> = ({ children }) => {
const showNotification = useCallback((notification: Notification) => {
setNotifications(prev => [...prev, notification]);
}, []);
const hideNotification = useCallback((id: string) => {
setNotifications(prev => prev.filter(n => n.id !== id));
}, []);
// Memoize the context value to prevent consumer re-renders
const contextValue = useMemo(() => ({
showNotification,
hideNotification,
}), [showNotification, hideNotification]);
return (
<FlashbarContext.Provider value={contextValue}>
{children}
<FlashbarComponent />
</FlashbarContext.Provider>
);
};
Using React.memo for the flashbar component prevents unnecessary re-renders when parent components update. The flashbar's props don't change frequently--only when switching between notifications--so memoization prevents wasted rendering cycles. The useMemo and useCallback hooks similarly optimize the context value and callback functions passed to child components.
Animation performance benefits from using the useNativeDriver option, which runs animations on the UI thread rather than the JavaScript thread. This prevents animation jank even during heavy JavaScript operations. Memory management becomes important when the flashbar component remains mounted but inactive. Unsubscribing from any listeners and clearing timers in the component's cleanup function prevents memory leaks.
Performance Checklist
- Enable useNativeDriver for all animations
- Use React.memo to prevent unnecessary re-renders
- Memoize context values with useMemo
- Clear timers and subscriptions in useEffect cleanup
- Consider lazy mounting for the flashbar component
Position
Choose top or bottom screen placement with configurable offsets
Themes
Custom color schemes and typography through theme objects
Custom Renderers
Completely customize notification appearance with render props
Animation
Configure timing, easing, and animation curves
Conclusion
Building a custom flashbar component for React Native involves multiple interconnected systems: animated transitions, notification queuing, accessibility support, and context-based state management. Understanding how these systems work together helps you create a robust implementation that enhances your application's user experience.
Key Takeaways
-
Start Simple: Begin with core functionality--animated display and dismissal with proper queue management--before adding advanced features like custom actions or theming
-
Test Thoroughly: Cover rendering, animations, and edge cases including rapid notifications and empty queue states
-
Consider Accessibility: Make your flashbar usable by everyone through proper labeling, color contrast, and reduced motion support
-
Optimize Performance: Use memoization and native animations to maintain smooth app performance
-
Design for Extension: Allow customization through props and configuration without requiring source modifications
Next Steps
- Explore existing libraries like react-native-snackbar-component for production-ready solutions
- Add advanced features like notification persistence across app restarts
- Implement network-aware notifications with automatic retry logic
- Create notification analytics to track engagement and identify patterns
The implementation described in this guide provides a foundation you can customize for your specific needs. Whether you're building a simple notification system for a mobile application or integrating notifications into a complex enterprise app, the patterns shown here will help you create a scalable, accessible, and performant solution.
For teams looking to implement custom React Native components as part of a broader web development strategy, investing in well-designed UI components like flashbars pays dividends in user experience and development velocity. Additionally, ensuring your app is discoverable through professional SEO services helps users find and engage with your application effectively. Consider partnering with experienced developers who understand the nuances of React Native animation and component architecture.
Frequently Asked Questions
What's the difference between a flashbar and a toast notification?
Toast notifications typically refer to simple, short-lived messages that appear without user interaction. Flashbars are more feature-rich, often supporting actions, multiple types, and queue management. The terms are sometimes used interchangeably in practice.
How long should a flashbar stay visible?
Standard duration is 3-5 seconds. Consider longer durations (8-10 seconds) for important messages or when supporting users who need more reading time. Allow configuration based on notification importance.
Can I use a library instead of building a custom flashbar?
Yes. Libraries like react-native-snackbar-component and flashbar packages provide tested solutions. However, understanding custom implementation helps you evaluate options and customize when needed.
How do I handle notifications when the app is in the background?
Flashbars are for foreground use only. Background notifications should use push notifications (FCM/APNs) with proper system integration and notification channels on Android.
What's the best position for flashbar notifications?
Position depends on your app's design. Top positioning aligns with status bars and system chrome. Bottom positioning feels more natural for thumb-based interaction. Many apps support both through configuration.
Sources
-
LogRocket: Build a custom flashbar in React Native - Comprehensive tutorial on flashbar architecture, Animated API implementation, and queue management
-
LogRocket: Using React Native Snackbar for custom pop-up messages - Guide on snackbar patterns, state management, and accessibility considerations