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 utility hooks adapted for web with comprehensive support for window dimensions, color scheme preferences, and locale context management for building responsive, theme-aware applications.
Hook for accessing current window dimensions with automatic updates on resize, providing responsive layout capabilities for web applications.
const useWindowDimensions: () => DisplayMetrics;Returns current window dimensions that update automatically when the window is resized.
interface DisplayMetrics {
fontScale: number;
height: number;
scale: number;
width: number;
}Usage:
import { useWindowDimensions } from "react-native-web";
function ResponsiveComponent() {
const { width, height, scale, fontScale } = useWindowDimensions();
// Responsive breakpoints
const isTablet = width >= 768;
const isDesktop = width >= 1024;
const isMobile = width < 768;
// Calculate responsive values
const padding = isDesktop ? 32 : isTablet ? 24 : 16;
const fontSize = isDesktop ? 18 : isTablet ? 16 : 14;
const columns = isDesktop ? 3 : isTablet ? 2 : 1;
return (
<View style={styles.container}>
<Text style={styles.info}>
Window: {width} × {height} (scale: {scale}, fontScale: {fontScale})
</Text>
<View style={[styles.content, { padding }]}>
<Text style={[styles.title, { fontSize }]}>
Responsive Design
</Text>
<Text style={styles.deviceType}>
Device Type: {isDesktop ? 'Desktop' : isTablet ? 'Tablet' : 'Mobile'}
</Text>
{/* Responsive grid */}
<View style={[styles.grid, { flexDirection: columns === 1 ? 'column' : 'row' }]}>
{Array.from({ length: 6 }, (_, i) => (
<View
key={i}
style={[
styles.gridItem,
{
width: columns === 1 ? '100%' : columns === 2 ? '50%' : '33.33%'
}
]}
>
<Text>Item {i + 1}</Text>
</View>
))}
</View>
</View>
</View>
);
}
// Custom responsive hooks
function useResponsiveValue(mobileValue, tabletValue, desktopValue) {
const { width } = useWindowDimensions();
return React.useMemo(() => {
if (width >= 1024) return desktopValue;
if (width >= 768) return tabletValue;
return mobileValue;
}, [width, mobileValue, tabletValue, desktopValue]);
}
function useBreakpoints() {
const { width, height } = useWindowDimensions();
return React.useMemo(() => ({
isMobile: width < 768,
isTablet: width >= 768 && width < 1024,
isDesktop: width >= 1024,
isLandscape: width > height,
isPortrait: height > width,
aspectRatio: width / height
}), [width, height]);
}
function useMediaQuery(query) {
const [matches, setMatches] = React.useState(false);
React.useEffect(() => {
if (typeof window === 'undefined') return;
const mediaQuery = window.matchMedia(query);
setMatches(mediaQuery.matches);
const handler = (event) => setMatches(event.matches);
mediaQuery.addEventListener('change', handler);
return () => mediaQuery.removeEventListener('change', handler);
}, [query]);
return matches;
}
// Advanced responsive layout component
function AdaptiveLayout({ children }) {
const { width } = useWindowDimensions();
const breakpoints = useBreakpoints();
// Calculate sidebar width based on screen size
const sidebarWidth = useResponsiveValue(0, 250, 300);
const showSidebar = breakpoints.isTablet || breakpoints.isDesktop;
return (
<View style={styles.adaptiveContainer}>
{showSidebar && (
<View style={[styles.sidebar, { width: sidebarWidth }]}>
<Text style={styles.sidebarTitle}>Navigation</Text>
{/* Navigation items */}
</View>
)}
<View style={[
styles.mainContent,
{ marginLeft: showSidebar ? sidebarWidth : 0 }
]}>
{children}
</View>
{/* Mobile navigation overlay */}
{breakpoints.isMobile && (
<TouchableOpacity style={styles.mobileMenuButton}>
<Text>☰</Text>
</TouchableOpacity>
)}
</View>
);
}Hook for detecting and responding to system color scheme preferences with automatic updates when the user changes their theme settings.
const useColorScheme: () => ColorSchemeName;Returns the current color scheme preference from the system.
type ColorSchemeName = 'light' | 'dark' | null;Usage:
import { useColorScheme } from "react-native-web";
function ThemedComponent() {
const colorScheme = useColorScheme();
const isDark = colorScheme === 'dark';
// Theme-aware styles
const theme = React.useMemo(() => ({
backgroundColor: isDark ? '#1a1a1a' : '#ffffff',
textColor: isDark ? '#ffffff' : '#000000',
borderColor: isDark ? '#333333' : '#e0e0e0',
cardBackground: isDark ? '#2d2d2d' : '#f8f9fa',
primaryColor: isDark ? '#4a9eff' : '#007AFF',
secondaryColor: isDark ? '#ff6b6b' : '#ff3b30'
}), [isDark]);
return (
<View style={[styles.container, { backgroundColor: theme.backgroundColor }]}>
<Text style={[styles.title, { color: theme.textColor }]}>
Current Theme: {colorScheme || 'system default'}
</Text>
<View style={[styles.card, {
backgroundColor: theme.cardBackground,
borderColor: theme.borderColor
}]}>
<Text style={[styles.cardText, { color: theme.textColor }]}>
This card adapts to your system theme preference
</Text>
<TouchableOpacity
style={[styles.button, { backgroundColor: theme.primaryColor }]}
>
<Text style={styles.buttonText}>Primary Button</Text>
</TouchableOpacity>
</View>
</View>
);
}
// Theme provider using color scheme
function ThemeProvider({ children }) {
const colorScheme = useColorScheme();
const theme = React.useMemo(() => {
const isDark = colorScheme === 'dark';
return {
colors: {
// Background colors
background: isDark ? '#000000' : '#ffffff',
surface: isDark ? '#1c1c1e' : '#f2f2f7',
card: isDark ? '#2c2c2e' : '#ffffff',
// Text colors
text: isDark ? '#ffffff' : '#000000',
textSecondary: isDark ? '#8e8e93' : '#6d6d70',
// UI colors
primary: isDark ? '#0a84ff' : '#007aff',
secondary: isDark ? '#ff453a' : '#ff3b30',
success: isDark ? '#32d74b' : '#34c759',
warning: isDark ? '#ff9f0a' : '#ff9500',
error: isDark ? '#ff453a' : '#ff3b30',
// Border colors
border: isDark ? '#38383a' : '#c6c6c8',
separator: isDark ? '#38383a' : '#c6c6c8'
},
spacing: {
xs: 4,
sm: 8,
md: 16,
lg: 24,
xl: 32
},
borderRadius: {
sm: 4,
md: 8,
lg: 12,
xl: 16
},
isDark,
colorScheme
};
}, [colorScheme]);
return (
<ThemeContext.Provider value={theme}>
{children}
</ThemeContext.Provider>
);
}
const ThemeContext = React.createContext(null);
function useTheme() {
const theme = React.useContext(ThemeContext);
if (!theme) {
throw new Error('useTheme must be used within a ThemeProvider');
}
return theme;
}
// Animated theme transitions
function AnimatedThemeComponent() {
const colorScheme = useColorScheme();
const [animatedValues] = React.useState({
backgroundColor: new Animated.Value(0),
textColor: new Animated.Value(0)
});
React.useEffect(() => {
const isDark = colorScheme === 'dark';
const targetValue = isDark ? 1 : 0;
Animated.parallel([
Animated.timing(animatedValues.backgroundColor, {
toValue: targetValue,
duration: 300,
useNativeDriver: false
}),
Animated.timing(animatedValues.textColor, {
toValue: targetValue,
duration: 300,
useNativeDriver: false
})
]).start();
}, [colorScheme]);
const animatedBackgroundColor = animatedValues.backgroundColor.interpolate({
inputRange: [0, 1],
outputRange: ['#ffffff', '#1a1a1a']
});
const animatedTextColor = animatedValues.textColor.interpolate({
inputRange: [0, 1],
outputRange: ['#000000', '#ffffff']
});
return (
<Animated.View style={[
styles.animatedContainer,
{ backgroundColor: animatedBackgroundColor }
]}>
<Animated.Text style={[
styles.animatedText,
{ color: animatedTextColor }
]}>
Smoothly animated theme transitions
</Animated.Text>
</Animated.View>
);
}
// Custom theme detection
function useSystemTheme() {
const [systemTheme, setSystemTheme] = React.useState('light');
React.useEffect(() => {
if (typeof window === 'undefined') return;
const darkModeQuery = window.matchMedia('(prefers-color-scheme: dark)');
const updateTheme = (e) => {
setSystemTheme(e.matches ? 'dark' : 'light');
};
// Set initial value
updateTheme(darkModeQuery);
// Listen for changes
darkModeQuery.addEventListener('change', updateTheme);
return () => darkModeQuery.removeEventListener('change', updateTheme);
}, []);
return systemTheme;
}Hook for accessing locale information including language preference and text direction for internationalization support.
const useLocaleContext: () => LocaleValue;Returns the current locale context with direction and language information.
interface LocaleValue {
direction: 'ltr' | 'rtl';
locale: string | null;
}Usage:
import { useLocaleContext } from "react-native-web";
function LocalizedComponent() {
const { direction, locale } = useLocaleContext();
const isRTL = direction === 'rtl';
return (
<View style={[styles.container, isRTL && styles.rtlContainer]}>
<Text style={[styles.title, isRTL && styles.rtlText]}>
Current Locale: {locale || 'Default'}
</Text>
<Text style={[styles.direction, isRTL && styles.rtlText]}>
Text Direction: {direction.toUpperCase()}
</Text>
{/* RTL-aware layout */}
<View style={[styles.row, isRTL && styles.rtlRow]}>
<View style={styles.startItem}>
<Text>Start Item</Text>
</View>
<View style={styles.centerItem}>
<Text>Center Item</Text>
</View>
<View style={styles.endItem}>
<Text>End Item</Text>
</View>
</View>
{/* RTL-aware text alignment */}
<Text style={[
styles.paragraph,
{ textAlign: isRTL ? 'right' : 'left' }
]}>
This paragraph text aligns according to the current text direction.
In RTL locales, it will be right-aligned, and in LTR locales, it will be left-aligned.
</Text>
</View>
);
}
// Locale provider usage
function App() {
const [currentLocale, setCurrentLocale] = React.useState('en-US');
return (
<LocaleProvider
locale={currentLocale}
direction={getLocaleDirection(currentLocale)}
>
<View style={styles.app}>
<LocaleSelector
currentLocale={currentLocale}
onLocaleChange={setCurrentLocale}
/>
<LocalizedComponent />
</View>
</LocaleProvider>
);
}
function LocaleSelector({ currentLocale, onLocaleChange }) {
const locales = [
{ code: 'en-US', name: 'English (US)', direction: 'ltr' },
{ code: 'ar-SA', name: 'العربية', direction: 'rtl' },
{ code: 'he-IL', name: 'עברית', direction: 'rtl' },
{ code: 'es-ES', name: 'Español', direction: 'ltr' },
{ code: 'fr-FR', name: 'Français', direction: 'ltr' },
{ code: 'ja-JP', name: '日本語', direction: 'ltr' }
];
return (
<View style={styles.localeSelector}>
<Text style={styles.selectorTitle}>Select Language:</Text>
{locales.map(({ code, name, direction }) => (
<TouchableOpacity
key={code}
style={[
styles.localeOption,
currentLocale === code && styles.selectedLocale
]}
onPress={() => onLocaleChange(code)}
>
<Text style={[
styles.localeName,
{ textAlign: direction === 'rtl' ? 'right' : 'left' }
]}>
{name}
</Text>
<Text style={styles.localeCode}>
{code} ({direction.toUpperCase()})
</Text>
</TouchableOpacity>
))}
</View>
);
}
// RTL-aware styling utilities
function useRTLStyles() {
const { direction } = useLocaleContext();
const isRTL = direction === 'rtl';
return React.useCallback((styles) => {
if (!isRTL) return styles;
const rtlStyles = { ...styles };
// Flip horizontal margins
if (styles.marginLeft !== undefined) {
rtlStyles.marginRight = styles.marginLeft;
rtlStyles.marginLeft = styles.marginRight || 0;
}
// Flip horizontal padding
if (styles.paddingLeft !== undefined) {
rtlStyles.paddingRight = styles.paddingLeft;
rtlStyles.paddingLeft = styles.paddingRight || 0;
}
// Flip text alignment
if (styles.textAlign === 'left') {
rtlStyles.textAlign = 'right';
} else if (styles.textAlign === 'right') {
rtlStyles.textAlign = 'left';
}
// Flip flex direction
if (styles.flexDirection === 'row') {
rtlStyles.flexDirection = 'row-reverse';
} else if (styles.flexDirection === 'row-reverse') {
rtlStyles.flexDirection = 'row';
}
return rtlStyles;
}, [isRTL]);
}
// Locale-aware formatting utilities
function useLocaleFormatting() {
const { locale } = useLocaleContext();
return React.useMemo(() => ({
formatNumber: (number, options = {}) => {
try {
return new Intl.NumberFormat(locale, options).format(number);
} catch {
return number.toString();
}
},
formatCurrency: (amount, currency = 'USD') => {
try {
return new Intl.NumberFormat(locale, {
style: 'currency',
currency
}).format(amount);
} catch {
return `${currency} ${amount}`;
}
},
formatDate: (date, options = {}) => {
try {
return new Intl.DateTimeFormat(locale, options).format(date);
} catch {
return date.toLocaleDateString();
}
},
formatRelativeTime: (value, unit = 'day') => {
try {
return new Intl.RelativeTimeFormat(locale).format(value, unit);
} catch {
return `${value} ${unit}${Math.abs(value) !== 1 ? 's' : ''} ago`;
}
}
}), [locale]);
}
function FormattingDemo() {
const formatting = useLocaleFormatting();
const { locale } = useLocaleContext();
const sampleData = {
number: 1234567.89,
currency: 299.99,
date: new Date(),
relativeTime: -3
};
return (
<View style={styles.formattingDemo}>
<Text style={styles.demoTitle}>
Locale Formatting ({locale})
</Text>
<View style={styles.formatExample}>
<Text style={styles.label}>Number:</Text>
<Text style={styles.value}>
{formatting.formatNumber(sampleData.number)}
</Text>
</View>
<View style={styles.formatExample}>
<Text style={styles.label}>Currency:</Text>
<Text style={styles.value}>
{formatting.formatCurrency(sampleData.currency)}
</Text>
</View>
<View style={styles.formatExample}>
<Text style={styles.label}>Date:</Text>
<Text style={styles.value}>
{formatting.formatDate(sampleData.date, {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric'
})}
</Text>
</View>
<View style={styles.formatExample}>
<Text style={styles.label}>Relative Time:</Text>
<Text style={styles.value}>
{formatting.formatRelativeTime(sampleData.relativeTime)}
</Text>
</View>
</View>
);
}Leverage multiple hooks together for comprehensive responsive and accessible applications.
function ResponsiveThemedApp() {
const dimensions = useWindowDimensions();
const colorScheme = useColorScheme();
const { direction, locale } = useLocaleContext();
// Combined responsive and theme-aware styling
const styles = React.useMemo(() => {
const isTablet = dimensions.width >= 768;
const isDesktop = dimensions.width >= 1024;
const isDark = colorScheme === 'dark';
const isRTL = direction === 'rtl';
return StyleSheet.create({
container: {
flex: 1,
backgroundColor: isDark ? '#000' : '#fff',
padding: isDesktop ? 32 : isTablet ? 24 : 16,
flexDirection: isRTL ? 'row-reverse' : 'row'
},
sidebar: {
width: isDesktop ? 300 : isTablet ? 250 : 0,
backgroundColor: isDark ? '#1a1a1a' : '#f8f9fa',
display: isTablet ? 'flex' : 'none'
},
content: {
flex: 1,
marginLeft: !isRTL && isTablet ? 16 : 0,
marginRight: isRTL && isTablet ? 16 : 0
},
title: {
fontSize: isDesktop ? 28 : isTablet ? 24 : 20,
color: isDark ? '#fff' : '#000',
textAlign: isRTL ? 'right' : 'left',
fontFamily: locale?.startsWith('ar') ? 'Noto Sans Arabic' : 'system'
}
});
}, [dimensions, colorScheme, direction, locale]);
return (
<View style={styles.container}>
<View style={styles.sidebar}>
<Text>Navigation Sidebar</Text>
</View>
<View style={styles.content}>
<Text style={styles.title}>
Responsive, Themed, and Localized Content
</Text>
<View>
<Text>Screen: {dimensions.width} × {dimensions.height}</Text>
<Text>Theme: {colorScheme}</Text>
<Text>Direction: {direction}</Text>
<Text>Locale: {locale}</Text>
</View>
</View>
</View>
);
}
// Hook composition for common patterns
function useAppPreferences() {
const dimensions = useWindowDimensions();
const colorScheme = useColorScheme();
const locale = useLocaleContext();
return React.useMemo(() => ({
// Device info
...dimensions,
isMobile: dimensions.width < 768,
isTablet: dimensions.width >= 768 && dimensions.width < 1024,
isDesktop: dimensions.width >= 1024,
// Theme info
colorScheme,
isDark: colorScheme === 'dark',
isLight: colorScheme === 'light',
// Locale info
...locale,
isRTL: locale.direction === 'rtl',
isLTR: locale.direction === 'ltr'
}), [dimensions, colorScheme, locale]);
}React Native Web's hooks leverage modern web APIs and standards:
useWindowDimensions:
window.innerWidth and window.innerHeightresize events for automatic updatesdevicePixelRatio as the scale valueuseColorScheme:
(prefers-color-scheme: dark)useLocaleContext:
LocaleProvider contextnavigator.languageinterface DisplayMetrics {
fontScale: number;
height: number;
scale: number;
width: number;
}
type ColorSchemeName = 'light' | 'dark' | null;
interface LocaleValue {
direction: 'ltr' | 'rtl';
locale: string | null;
}
type WritingDirection = 'ltr' | 'rtl';
interface LocaleProviderProps {
direction?: WritingDirection;
locale?: string;
children: React.ReactNode;
}
// Hook return types
type UseWindowDimensionsReturn = DisplayMetrics;
type UseColorSchemeReturn = ColorSchemeName;
type UseLocaleContextReturn = LocaleValue;const styles = StyleSheet.create({ container: { flex: 1, padding: 20 }, info: { fontSize: 14, marginBottom: 16, color: '#666' }, content: { flex: 1 }, title: { fontWeight: 'bold', marginBottom: 16 }, deviceType: { fontSize: 16, marginBottom: 20, fontWeight: '600' }, grid: { flexWrap: 'wrap' }, gridItem: { padding: 16, backgroundColor: '#f0f0f0', margin: 4, borderRadius: 8, alignItems: 'center' },
// Adaptive layout styles adaptiveContainer: { flex: 1, flexDirection: 'row' }, sidebar: { backgroundColor: '#f8f9fa', padding: 20, borderRightWidth: 1, borderRightColor: '#e0e0e0' }, sidebarTitle: { fontSize: 18, fontWeight: 'bold', marginBottom: 16 }, mainContent: { flex: 1, padding: 20 }, mobileMenuButton: { position: 'absolute', top: 20, left: 20, width: 44, height: 44, backgroundColor: '#007AFF', borderRadius: 22, justifyContent: 'center', alignItems: 'center' },
// Theme styles card: { padding: 20, borderRadius: 12, borderWidth: 1, marginVertical: 16 }, cardText: { fontSize: 16, marginBottom: 16 }, button: { paddingVertical: 12, paddingHorizontal: 24, borderRadius: 8, alignItems: 'center' }, buttonText: { color: 'white', fontWeight: '600' },
// Animated theme styles animatedContainer: { padding: 20, borderRadius: 12, margin: 20 }, animatedText: { fontSize: 16, textAlign: 'center' },
// Locale styles rtlContainer: { flexDirection: 'row-reverse' }, rtlText: { textAlign: 'right' }, rtlRow: { flexDirection: 'row-reverse' }, row: { flexDirection: 'row', marginVertical: 16 }, startItem: { flex: 1, backgroundColor: '#e3f2fd', padding: 16, marginHorizontal: 4, borderRadius: 8, alignItems: 'center' }, centerItem: { flex: 1, backgroundColor: '#f3e5f5', padding: 16, marginHorizontal: 4, borderRadius: 8, alignItems: 'center' }, endItem: { flex: 1, backgroundColor: '#e8f5e8', padding: 16, marginHorizontal: 4, borderRadius: 8, alignItems: 'center' }, paragraph: { fontSize: 16, lineHeight: 24, marginVertical: 16 },
// Locale selector styles localeSelector: { marginBottom: 24 }, selectorTitle: { fontSize: 18, fontWeight: 'bold', marginBottom: 12 }, localeOption: { padding: 12, borderWidth: 1, borderColor: '#e0e0e0', borderRadius: 8, marginVertical: 4 }, selectedLocale: { backgroundColor: '#e3f2fd', borderColor: '#2196f3' }, localeName: { fontSize: 16, fontWeight: '600', marginBottom: 4 }, localeCode: { fontSize: 12, color: '#666' },
// Formatting demo styles formattingDemo: { padding: 16, backgroundColor: '#f8f9fa', borderRadius: 8, marginTop: 16 }, demoTitle: { fontSize: 18, fontWeight: 'bold', marginBottom: 16 }, formatExample: { flexDirection: 'row', justifyContent: 'space-between', marginVertical: 8, paddingVertical: 8, borderBottomWidth: 1, borderBottomColor: '#e0e0e0' }, label: { fontWeight: '600', color: '#333' }, value: { color: '#666' } });
Install with Tessl CLI
npx tessl i tessl/npm-react-native-web