React Native for Web is a comprehensive compatibility library that enables React Native components and APIs to run seamlessly on web browsers using React DOM.
—
React Native's platform APIs adapted for web environments, including device information, system services, web integrations, and cross-platform utilities.
Provides platform detection and conditional code execution with web-specific implementations.
const Platform: {
OS: 'web';
Version: string;
isTesting: boolean;
select: <T>(specifics: {web?: T, default?: T}) => T;
};Properties:
OS - Always 'web' for React Native WebVersion - Platform version (always '0.0.0' for web)isTesting - Whether running in test environmentMethods:
select(obj) - Select platform-specific valuesUsage:
import { Platform } from "react-native-web";
// Check platform
if (Platform.OS === 'web') {
console.log('Running on web');
}
// Platform-specific values
const styles = StyleSheet.create({
container: {
flex: 1,
paddingTop: Platform.select({
web: 20,
default: 0
})
},
text: {
fontSize: Platform.select({
web: 16,
default: 14
})
}
});
// Platform-specific components
const HeaderComponent = Platform.select({
web: WebHeader,
default: DefaultHeader
});
// Conditional rendering
function App() {
return (
<View>
<Text>Hello React Native Web!</Text>
{Platform.OS === 'web' && (
<Text>This only shows on web</Text>
)}
</View>
);
}
// Environment detection
const isProduction = !Platform.isTesting && process.env.NODE_ENV === 'production';Component and API for controlling the status bar appearance and behavior. On web, this is a compatibility component that provides no-op implementations for mobile-specific status bar functionality.
const StatusBar: React.ComponentType<StatusBarProps> & {
setBackgroundColor: (color: string, animated?: boolean) => void;
setBarStyle: (style: 'default' | 'light-content' | 'dark-content', animated?: boolean) => void;
setHidden: (hidden: boolean, animation?: 'none' | 'fade' | 'slide') => void;
setNetworkActivityIndicatorVisible: (visible: boolean) => void;
setTranslucent: (translucent: boolean) => void;
};Web Implementation: StatusBar on web is a compatibility layer that renders null and provides no-op static methods. This ensures cross-platform compatibility for apps that use StatusBar on mobile platforms.
<StatusBar barStyle="dark-content" backgroundColor="#ffffff" hidden={false} />// All static methods are no-ops on web but maintain compatibility
StatusBar.setBackgroundColor(color: string, animated?: boolean): void
StatusBar.setBarStyle(style: 'default' | 'light-content' | 'dark-content', animated?: boolean): void
StatusBar.setHidden(hidden: boolean, animation?: 'none' | 'fade' | 'slide'): void
StatusBar.setNetworkActivityIndicatorVisible(visible: boolean): void
StatusBar.setTranslucent(translucent: boolean): voidUsage:
import React from "react";
import { StatusBar, View, Text } from "react-native-web";
function App() {
return (
<View style={{ flex: 1 }}>
{/* StatusBar component renders nothing on web but maintains compatibility */}
<StatusBar
barStyle="dark-content"
backgroundColor="#ffffff"
hidden={false}
/>
<Text>App content here</Text>
</View>
);
}
// Static methods are no-ops on web
function setStatusBarStyle() {
// These calls do nothing on web but don't throw errors
StatusBar.setBarStyle('light-content');
StatusBar.setBackgroundColor('#000000');
StatusBar.setHidden(true, 'fade');
}Cross-Platform Considerations:
import { Platform, StatusBar } from "react-native-web";
function ConfigureStatusBar() {
// Only configure status bar on mobile platforms
if (Platform.OS !== 'web') {
StatusBar.setBarStyle('light-content');
StatusBar.setBackgroundColor('#1a1a1a');
}
// Or use the component approach (safe on all platforms)
return (
<StatusBar
barStyle="light-content"
backgroundColor="#1a1a1a"
/>
);
}Utilities for accessing screen and window dimensions with responsive design support and automatic updates.
const Dimensions: {
get: (dimension: 'window' | 'screen') => DisplayMetrics;
addEventListener: (type: 'change', handler: (dimensions: DimensionsValue) => void) => EventSubscription;
removeEventListener: (type: 'change', handler: Function) => void;
set: (initialDimensions?: DimensionsValue) => void;
};DisplayMetrics:
{
width: number; // Width in CSS pixels
height: number; // Height in CSS pixels
scale: number; // Device pixel ratio
fontScale: number; // Font scale factor (always 1 on web)
}Dimensions:
window - Viewport dimensions (changes with browser resize)screen - Screen dimensions (full screen size)Usage:
import { Dimensions } from "react-native-web";
// Get dimensions
const windowDimensions = Dimensions.get('window');
const screenDimensions = Dimensions.get('screen');
console.log('Window:', windowDimensions);
// { width: 1024, height: 768, scale: 2, fontScale: 1 }
console.log('Screen:', screenDimensions);
// { width: 1920, height: 1080, scale: 2, fontScale: 1 }
// Responsive design
function ResponsiveComponent() {
const [dimensions, setDimensions] = useState(Dimensions.get('window'));
useEffect(() => {
const subscription = Dimensions.addEventListener('change', ({ window }) => {
setDimensions(window);
});
return () => subscription?.remove();
}, []);
const isTablet = dimensions.width >= 768;
const isDesktop = dimensions.width >= 1024;
return (
<View style={[
styles.container,
isTablet && styles.tablet,
isDesktop && styles.desktop
]}>
<Text>Current width: {dimensions.width}px</Text>
<Text>Device type: {isDesktop ? 'Desktop' : isTablet ? 'Tablet' : 'Mobile'}</Text>
</View>
);
}
// Breakpoint utilities
const breakpoints = {
mobile: 0,
tablet: 768,
desktop: 1024,
wide: 1200
};
function getBreakpoint(width) {
if (width >= breakpoints.wide) return 'wide';
if (width >= breakpoints.desktop) return 'desktop';
if (width >= breakpoints.tablet) return 'tablet';
return 'mobile';
}
// Layout calculations
function calculateLayout() {
const { width, height } = Dimensions.get('window');
const isLandscape = width > height;
const aspectRatio = width / height;
return {
isLandscape,
aspectRatio,
gridColumns: Math.floor(width / 300), // 300px per column
itemWidth: (width - 40) / 2 // 20px margin, 2 columns
};
}Application state management for detecting when the app becomes active, inactive, or goes to background using the Page Visibility API.
const AppState: {
currentState: 'active' | 'background';
isAvailable: boolean;
addEventListener: (type: 'change' | 'memoryWarning', handler: (state: string) => void) => EventSubscription;
};States:
active - App is in foreground and visiblebackground - App is in background (page is hidden)Usage:
import { AppState } from "react-native-web";
// Get current state
console.log('Current state:', AppState.currentState);
// Listen for state changes
function AppStateExample() {
const [appState, setAppState] = useState(AppState.currentState);
useEffect(() => {
const handleAppStateChange = (nextAppState) => {
console.log('App state changed to:', nextAppState);
setAppState(nextAppState);
};
const subscription = AppState.addEventListener('change', handleAppStateChange);
return () => {
subscription?.remove();
};
}, []);
return (
<View>
<Text>App State: {appState}</Text>
{appState === 'active' && (
<Text>App is active and visible</Text>
)}
{appState === 'background' && (
<Text>App is in background</Text>
)}
</View>
);
}
// Pause video when app goes to background
function VideoPlayer() {
const [isPaused, setIsPaused] = useState(false);
useEffect(() => {
const handleAppStateChange = (nextAppState) => {
if (nextAppState === 'background') {
setIsPaused(true);
} else if (nextAppState === 'active') {
// Optionally resume when app becomes active
// setIsPaused(false);
}
};
AppState.addEventListener('change', handleAppStateChange);
}, []);
return (
<Video
source={{ uri: 'video.mp4' }}
paused={isPaused}
onPress={() => setIsPaused(!isPaused)}
/>
);
}
// Auto-save when app goes to background
function AutoSaveForm() {
const [formData, setFormData] = useState({});
useEffect(() => {
const handleAppStateChange = (nextAppState) => {
if (nextAppState === 'background') {
// Save form data
localStorage.setItem('formData', JSON.stringify(formData));
}
};
AppState.addEventListener('change', handleAppStateChange);
}, [formData]);
// ... rest of component
}Utilities for working with device pixel density and converting between logical and physical pixels.
const PixelRatio: {
get: () => number;
getFontScale: () => number;
getPixelSizeForLayoutSize: (layoutSize: number) => number;
roundToNearestPixel: (layoutSize: number) => number;
};Methods:
get() - Returns device pixel ratio (e.g., 2 for Retina displays)getFontScale() - Returns font scale multiplier (always 1 on web)getPixelSizeForLayoutSize(dp) - Convert density-independent pixels to actual pixelsroundToNearestPixel(dp) - Round to nearest pixel boundaryUsage:
import { PixelRatio } from "react-native-web";
// Get pixel ratio
const pixelRatio = PixelRatio.get();
console.log('Device pixel ratio:', pixelRatio); // 2 on Retina displays
// Convert logical pixels to physical pixels
const logicalSize = 100;
const physicalSize = PixelRatio.getPixelSizeForLayoutSize(logicalSize);
console.log(`${logicalSize}dp = ${physicalSize}px`);
// Round to pixel boundaries for crisp rendering
const crispSize = PixelRatio.roundToNearestPixel(100.7);
console.log('Crisp size:', crispSize); // 100.5 on 2x display
// High-DPI image selection
function HighDPIImage({ src, alt, width, height }) {
const pixelRatio = PixelRatio.get();
const imageSrc = pixelRatio > 1 ? `${src}@2x.png` : `${src}.png`;
return (
<img
src={imageSrc}
alt={alt}
width={width}
height={height}
style={{
width: PixelRatio.roundToNearestPixel(width),
height: PixelRatio.roundToNearestPixel(height)
}}
/>
);
}
// Pixel-perfect borders
const styles = StyleSheet.create({
separator: {
height: 1 / PixelRatio.get(), // Always 1 physical pixel
backgroundColor: '#ccc'
},
crispContainer: {
width: PixelRatio.roundToNearestPixel(200.3),
height: PixelRatio.roundToNearestPixel(150.7),
borderWidth: 1 / PixelRatio.get()
}
});Keyboard utilities with limited web functionality. Mainly provides compatibility with React Native code.
const Keyboard: {
dismiss: () => void;
addListener: (eventType: string, callback: Function) => EventSubscription;
removeListener: (eventType: string, callback: Function) => void;
removeAllListeners: (eventType: string) => void;
isVisible: () => boolean;
};Usage:
import { Keyboard } from "react-native-web";
// Dismiss keyboard (blurs active input)
function DismissKeyboard() {
const handleSubmit = () => {
// Process form
Keyboard.dismiss();
};
return (
<View>
<TextInput placeholder="Type here..." />
<Button title="Submit" onPress={handleSubmit} />
</View>
);
}
// Note: Keyboard event listeners are no-ops on web
// Use standard DOM events or Visual Viewport API for keyboard detection
function WebKeyboardDetection() {
const [keyboardVisible, setKeyboardVisible] = useState(false);
useEffect(() => {
function handleResize() {
// Simple heuristic: significant height reduction might indicate keyboard
const currentHeight = window.innerHeight;
const isKeyboardVisible = currentHeight < window.screen.height * 0.8;
setKeyboardVisible(isKeyboardVisible);
}
// Better approach: Use Visual Viewport API if available
if (window.visualViewport) {
window.visualViewport.addEventListener('resize', handleResize);
return () => window.visualViewport.removeEventListener('resize', handleResize);
} else {
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}
}, []);
return (
<View style={{ paddingBottom: keyboardVisible ? 20 : 0 }}>
<Text>Keyboard is {keyboardVisible ? 'visible' : 'hidden'}</Text>
</View>
);
}Android back button handler. On web, this is a no-op for React Native compatibility.
const BackHandler: {
addEventListener: () => EventSubscription;
removeEventListener: () => void;
exitApp: () => void;
};Usage:
import { BackHandler, Platform } from "react-native-web";
// Cross-platform back handling
function NavigationHandler() {
useEffect(() => {
if (Platform.OS !== 'web') {
const backHandler = BackHandler.addEventListener('hardwareBackPress', () => {
// Handle back button on Android
return true; // Prevent default behavior
});
return () => backHandler.remove();
}
}, []);
// For web, use browser history or custom navigation
return <YourComponent />;
}
// Web-specific back button handling
function WebBackButton() {
const navigate = useNavigate(); // React Router or similar
const handleBack = () => {
if (Platform.OS === 'web') {
// Use browser history
window.history.back();
// Or programmatic navigation
// navigate(-1);
} else {
// Let BackHandler handle it on mobile
}
};
return (
<TouchableOpacity onPress={handleBack}>
<Text>← Back</Text>
</TouchableOpacity>
);
}Global event emitter for cross-component communication and device events.
const DeviceEventEmitter: {
addListener: (eventType: string, callback: Function) => EventSubscription;
removeListener: (eventType: string, callback: Function) => void;
emit: (eventType: string, data?: any) => void;
removeAllListeners: (eventType?: string) => void;
};Usage:
import { DeviceEventEmitter } from "react-native-web";
// Global event communication
function EventEmitterExample() {
useEffect(() => {
const subscription = DeviceEventEmitter.addListener('customEvent', (data) => {
console.log('Received event:', data);
});
return () => subscription.remove();
}, []);
const emitEvent = () => {
DeviceEventEmitter.emit('customEvent', {
message: 'Hello from emitter!',
timestamp: Date.now()
});
};
return (
<Button title="Emit Event" onPress={emitEvent} />
);
}
// Cross-component notifications
class NotificationService {
static notify(type, message) {
DeviceEventEmitter.emit('notification', { type, message });
}
static success(message) {
this.notify('success', message);
}
static error(message) {
this.notify('error', message);
}
}
function NotificationListener() {
const [notification, setNotification] = useState(null);
useEffect(() => {
const subscription = DeviceEventEmitter.addListener('notification', (data) => {
setNotification(data);
setTimeout(() => setNotification(null), 3000);
});
return () => subscription.remove();
}, []);
if (!notification) return null;
return (
<View style={[
styles.notification,
{ backgroundColor: notification.type === 'error' ? 'red' : 'green' }
]}>
<Text style={styles.notificationText}>{notification.message}</Text>
</View>
);
}interface DisplayMetrics {
width: number;
height: number;
scale: number;
fontScale: number;
}
interface DimensionsValue {
window: DisplayMetrics;
screen: DisplayMetrics;
}
interface EventSubscription {
remove: () => void;
}
interface PlatformStatic {
OS: 'web';
Version: string;
isTesting: boolean;
select: <T>(specifics: {web?: T, default?: T}) => T;
}
interface DimensionsStatic {
get: (dimension: 'window' | 'screen') => DisplayMetrics;
addEventListener: (type: 'change', handler: (dimensions: DimensionsValue) => void) => EventSubscription;
removeEventListener: (type: 'change', handler: Function) => void;
set: (initialDimensions?: DimensionsValue) => void;
}
interface AppStateStatic {
currentState: 'active' | 'background';
isAvailable: boolean;
addEventListener: (type: 'change' | 'memoryWarning', handler: (state: string) => void) => EventSubscription | undefined;
}
interface PixelRatioStatic {
get: () => number;
getFontScale: () => number;
getPixelSizeForLayoutSize: (layoutSize: number) => number;
roundToNearestPixel: (layoutSize: number) => number;
}
interface KeyboardStatic {
dismiss: () => void;
addListener: (eventType: string, callback: Function) => EventSubscription;
removeListener: (eventType: string, callback: Function) => void;
removeAllListeners: (eventType?: string) => void;
isVisible: () => boolean;
}
interface BackHandlerStatic {
addEventListener: (type: 'hardwareBackPress', handler: () => boolean) => EventSubscription;
removeEventListener: (type: 'hardwareBackPress', handler: Function) => void;
exitApp: () => void;
}
interface StatusBarProps {
animated?: boolean;
backgroundColor?: string;
barStyle?: 'default' | 'light-content' | 'dark-content';
hidden?: boolean;
networkActivityIndicatorVisible?: boolean;
showHideTransition?: 'fade' | 'slide';
translucent?: boolean;
}Install with Tessl CLI
npx tessl i tessl/npm-react-native-web