CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-react-native-web

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.

Pending
Overview
Eval results
Files

hooks.mddocs/

Hooks

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.

useWindowDimensions

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>
  );
}

useColorScheme

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;
}

useLocaleContext

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>
  );
}

Combined Usage Patterns

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]);
}

Web-Specific Implementation

React Native Web's hooks leverage modern web APIs and standards:

useWindowDimensions:

  • Uses window.innerWidth and window.innerHeight
  • Listens to resize events for automatic updates
  • Provides devicePixelRatio as the scale value
  • FontScale defaults to 1 (can be customized based on user preferences)

useColorScheme:

  • Uses CSS media query (prefers-color-scheme: dark)
  • Automatically updates when system preference changes
  • Falls back to 'light' if preference is not available
  • Compatible with all modern browsers

useLocaleContext:

  • Integrates with LocaleProvider context
  • Can detect RTL languages automatically
  • Supports browser language detection via navigator.language
  • Provides utilities for locale-aware formatting

Types

interface 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

docs

accessibility.md

animation.md

core-utilities.md

form-controls.md

hooks.md

index.md

interactive-components.md

layout-components.md

list-components.md

media-components.md

platform-apis.md

stylesheet.md

system-integration.md

text-input.md

tile.json