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

system-integration.mddocs/

System Integration

React Native's system integration APIs adapted for web, providing access to device capabilities, clipboard operations, URL handling, sharing functionality, and internationalization support.

AppRegistry

The JavaScript entry point for running React Native applications on the web. AppRegistry provides the core application lifecycle management for registering and running React Native Web apps.

const AppRegistry: {
  registerComponent: (appKey: string, componentProvider: () => React.ComponentType) => string;
  runApplication: (appKey: string, appParameters: AppParameters) => void;
  getAppKeys: () => string[];
  getApplication: (appKey: string, appParameters?: AppParameters) => {element: React.ReactNode, getStyleElement: (any) => React.ReactNode};
  registerConfig: (config: AppConfig[]) => void;
  registerRunnable: (appKey: string, run: Function) => string;
  unmountApplicationComponentAtRootTag: (rootTag: Element) => void;
  setComponentProviderInstrumentationHook: (hook: ComponentProviderInstrumentationHook) => void;
  setWrapperComponentProvider: (provider: WrapperComponentProvider) => void;
};

Web Implementation: AppRegistry on web manages the mounting and lifecycle of React applications, providing compatibility with React Native's application registration pattern while integrating with web DOM elements.

registerComponent()

Registers a React component as a runnable application that can be started with runApplication.

AppRegistry.registerComponent(appKey: string, componentProvider: () => React.ComponentType): string

runApplication()

Runs a registered application by mounting it to a DOM element. This is the primary method for starting React Native Web applications.

AppRegistry.runApplication(appKey: string, appParameters: AppParameters): void

Usage:

import React from "react";
import { AppRegistry, View, Text, StyleSheet } from "react-native-web";

// Define your app component
const App = () => (
  <View style={styles.container}>
    <Text style={styles.title}>Hello React Native Web!</Text>
  </View>
);

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: "center",
    alignItems: "center",
    backgroundColor: "#f5f5f5",
  },
  title: {
    fontSize: 24,
    fontWeight: "bold",
    color: "#333",
  },
});

// Register the component
AppRegistry.registerComponent("MyApp", () => App);

// Run the application (typically done once at app startup)
AppRegistry.runApplication("MyApp", {
  rootTag: document.getElementById("root"),
});

getAppKeys()

Returns an array of all registered application keys.

AppRegistry.getAppKeys(): string[]

getApplication()

Gets the application configuration for server-side rendering or advanced use cases.

AppRegistry.getApplication(appKey: string, appParameters?: AppParameters): {element: React.ReactNode, getStyleElement: (any) => React.ReactNode}

Appearance

System appearance and color scheme detection API that responds to user theme preferences and provides real-time updates when the color scheme changes.

const Appearance: {
  getColorScheme: () => 'light' | 'dark';
  addChangeListener: (listener: (preferences: AppearancePreferences) => void) => {remove: () => void};
};

Web Implementation: Uses the CSS media query (prefers-color-scheme: dark) to detect system theme preferences and provides listeners for theme changes.

getColorScheme()

Returns the current color scheme preference based on system settings.

Appearance.getColorScheme(): 'light' | 'dark'

addChangeListener()

Adds a listener for color scheme changes. Returns an object with a remove method to unsubscribe.

Appearance.addChangeListener(listener: (preferences: AppearancePreferences) => void): {remove: () => void}

Usage:

import React, { useState, useEffect } from "react";
import { Appearance, View, Text, StyleSheet } from "react-native-web";

function ThemedApp() {
  const [colorScheme, setColorScheme] = useState(Appearance.getColorScheme());

  useEffect(() => {
    const subscription = Appearance.addChangeListener(({ colorScheme }) => {
      setColorScheme(colorScheme);
    });

    return () => subscription.remove();
  }, []);

  const styles = colorScheme === 'dark' ? darkStyles : lightStyles;
  
  return (
    <View style={styles.container}>
      <Text style={styles.title}>
        Current theme: {colorScheme}
      </Text>
    </View>
  );
}

const lightStyles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#ffffff',
    justifyContent: 'center',
    alignItems: 'center',
  },
  title: {
    color: '#000000',
    fontSize: 18,
  },
});

const darkStyles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#000000',
    justifyContent: 'center',
    alignItems: 'center',
  },
  title: {
    color: '#ffffff',
    fontSize: 18,
  },
});

Alert

Browser-compatible alert system for displaying modal dialogs and user notifications with customizable actions and styling.

const Alert: {
  alert: (title?: string, message?: string, buttons?: AlertButton[], options?: AlertOptions) => void;
};

Web Implementation: The web implementation provides a minimal interface that can be extended with custom modal implementations. The default implementation uses browser APIs where available.

Alert.alert(): void

Usage:

import { Alert } from "react-native-web";

// Basic usage (web implementation is minimal)
function ShowAlert() {
  const handlePress = () => {
    Alert.alert();
  };

  return (
    <TouchableOpacity onPress={handlePress}>
      <Text>Show Alert</Text>
    </TouchableOpacity>
  );
}

// Custom alert implementation for web
function CustomAlert({ visible, title, message, buttons, onDismiss }) {
  if (!visible) return null;

  return (
    <View style={styles.overlay}>
      <View style={styles.alertContainer}>
        {title && <Text style={styles.title}>{title}</Text>}
        {message && <Text style={styles.message}>{message}</Text>}
        
        <View style={styles.buttonContainer}>
          {buttons?.map((button, index) => (
            <TouchableOpacity
              key={index}
              style={[styles.button, button.style === 'destructive' && styles.destructiveButton]}
              onPress={() => {
                button.onPress?.();
                onDismiss();
              }}
            >
              <Text style={[styles.buttonText, button.style === 'destructive' && styles.destructiveText]}>
                {button.text}
              </Text>
            </TouchableOpacity>
          ))}
        </View>
      </View>
    </View>
  );
}

const styles = StyleSheet.create({
  overlay: {
    position: 'absolute',
    top: 0,
    left: 0,
    right: 0,
    bottom: 0,
    backgroundColor: 'rgba(0,0,0,0.5)',
    justifyContent: 'center',
    alignItems: 'center',
    zIndex: 1000
  },
  alertContainer: {
    backgroundColor: 'white',
    borderRadius: 12,
    padding: 20,
    minWidth: 300,
    maxWidth: 400,
    margin: 20
  },
  title: {
    fontSize: 18,
    fontWeight: 'bold',
    marginBottom: 8,
    textAlign: 'center'
  },
  message: {
    fontSize: 14,
    marginBottom: 20,
    textAlign: 'center',
    color: '#666'
  },
  buttonContainer: {
    flexDirection: 'row',
    justifyContent: 'space-between'
  },
  button: {
    flex: 1,
    padding: 12,
    marginHorizontal: 4,
    backgroundColor: '#007AFF',
    borderRadius: 8,
    alignItems: 'center'
  },
  destructiveButton: {
    backgroundColor: '#FF3B30'
  },
  buttonText: {
    color: 'white',
    fontWeight: '600'
  },
  destructiveText: {
    color: 'white'
  }
});

Clipboard

Web-compatible clipboard operations for reading and writing text content with fallback implementations for older browsers.

const Clipboard: {
  isAvailable: () => boolean;
  getString: () => Promise<string>;
  setString: (text: string) => boolean;
};

isAvailable()

Check if clipboard operations are supported in the current browser.

Clipboard.isAvailable(): boolean

getString()

Get text content from the clipboard (web implementation returns empty string).

Clipboard.getString(): Promise<string>

setString()

Set text content to the clipboard using browser APIs.

Clipboard.setString(text: string): boolean

Usage:

import { Clipboard } from "react-native-web";

function ClipboardDemo() {
  const [clipboardText, setClipboardText] = React.useState('');
  const [status, setStatus] = React.useState('');

  const copyToClipboard = (text) => {
    if (Clipboard.isAvailable()) {
      const success = Clipboard.setString(text);
      setStatus(success ? 'Copied to clipboard!' : 'Failed to copy');
      
      // Clear status after 2 seconds
      setTimeout(() => setStatus(''), 2000);
    } else {
      setStatus('Clipboard not available');
    }
  };

  const pasteFromClipboard = async () => {
    try {
      // Modern browser API
      if (navigator.clipboard && navigator.clipboard.readText) {
        const text = await navigator.clipboard.readText();
        setClipboardText(text);
        setStatus('Pasted from clipboard!');
      } else {
        // Fallback for react-native-web implementation
        const text = await Clipboard.getString();
        setClipboardText(text);
        setStatus('Retrieved from clipboard');
      }
    } catch (error) {
      setStatus('Failed to read clipboard');
      console.error('Clipboard error:', error);
    }
  };

  return (
    <View style={styles.container}>
      <Text>Clipboard Operations</Text>
      
      <TextInput
        style={styles.input}
        placeholder="Enter text to copy"
        value={clipboardText}
        onChangeText={setClipboardText}
      />
      
      <View style={styles.buttonRow}>
        <TouchableOpacity 
          style={styles.button}
          onPress={() => copyToClipboard(clipboardText)}
        >
          <Text style={styles.buttonText}>Copy Text</Text>
        </TouchableOpacity>
        
        <TouchableOpacity 
          style={styles.button}
          onPress={pasteFromClipboard}
        >
          <Text style={styles.buttonText}>Paste Text</Text>
        </TouchableOpacity>
      </View>
      
      {status ? <Text style={styles.status}>{status}</Text> : null}
      
      <View style={styles.presetButtons}>
        <TouchableOpacity 
          style={styles.presetButton}
          onPress={() => copyToClipboard('https://react-native-web.js.org')}
        >
          <Text style={styles.buttonText}>Copy URL</Text>
        </TouchableOpacity>
        
        <TouchableOpacity 
          style={styles.presetButton}
          onPress={() => copyToClipboard('contact@example.com')}
        >
          <Text style={styles.buttonText}>Copy Email</Text>
        </TouchableOpacity>
      </View>
    </View>
  );
}

// Enhanced clipboard with modern API
class EnhancedClipboard {
  static async write(text) {
    try {
      if (navigator.clipboard && navigator.clipboard.writeText) {
        await navigator.clipboard.writeText(text);
        return true;
      } else {
        return Clipboard.setString(text);
      }
    } catch (error) {
      console.error('Clipboard write failed:', error);
      return false;
    }
  }

  static async read() {
    try {
      if (navigator.clipboard && navigator.clipboard.readText) {
        return await navigator.clipboard.readText();
      } else {
        return await Clipboard.getString();
      }
    } catch (error) {
      console.error('Clipboard read failed:', error);
      return '';
    }
  }

  static async writeRichText(html, text) {
    try {
      if (navigator.clipboard && navigator.clipboard.write) {
        const clipboardItems = [
          new ClipboardItem({
            'text/html': new Blob([html], { type: 'text/html' }),
            'text/plain': new Blob([text], { type: 'text/plain' })
          })
        ];
        await navigator.clipboard.write(clipboardItems);
        return true;
      } else {
        // Fallback to plain text
        return await this.write(text);
      }
    } catch (error) {
      console.error('Rich text clipboard failed:', error);
      return false;
    }
  }
}

Linking

URL handling and deep linking capabilities adapted for web environments with support for navigation, external links, and URL event handling.

const Linking: {
  addEventListener: (eventType: string, callback: Function) => { remove: () => void };
  removeEventListener: (eventType: string, callback: Function) => void;
  canOpenURL: (url: string) => Promise<boolean>;
  getInitialURL: () => Promise<string>;
  openURL: (url: string, target?: string) => Promise<void>;
};

addEventListener()

Listen for URL change events and other linking-related events.

Linking.addEventListener(
  eventType: string,
  callback: (...args: any[]) => void
): { remove: () => void }

canOpenURL()

Check if a URL can be opened (always returns true on web).

Linking.canOpenURL(url: string): Promise<boolean>

getInitialURL()

Get the initial URL that opened the app (current page URL on web).

Linking.getInitialURL(): Promise<string>

openURL()

Open a URL in the browser with configurable target window.

Linking.openURL(url: string, target?: string): Promise<void>

Usage:

import { Linking } from "react-native-web";

function LinkingDemo() {
  const [currentUrl, setCurrentUrl] = React.useState('');

  React.useEffect(() => {
    // Get initial URL
    Linking.getInitialURL().then(setCurrentUrl);

    // Listen for URL changes (web-specific implementation)
    const subscription = Linking.addEventListener('onOpen', (event) => {
      console.log('URL opened:', event);
      setCurrentUrl(event.url || window.location.href);
    });

    // Cleanup
    return () => subscription.remove();
  }, []);

  const openExternalLink = (url) => {
    Linking.canOpenURL(url).then(supported => {
      if (supported) {
        Linking.openURL(url, '_blank');
      } else {
        console.log("Don't know how to open URI: " + url);
      }
    });
  };

  const openInSameTab = (url) => {
    Linking.openURL(url, '_self');
  };

  const openEmail = (email, subject = '', body = '') => {
    const emailUrl = `mailto:${email}?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(body)}`;
    Linking.openURL(emailUrl);
  };

  const openPhone = (phoneNumber) => {
    const phoneUrl = `tel:${phoneNumber}`;
    Linking.openURL(phoneUrl);
  };

  return (
    <View style={styles.container}>
      <Text style={styles.title}>Linking Demo</Text>
      <Text style={styles.currentUrl}>Current URL: {currentUrl}</Text>
      
      <View style={styles.buttonContainer}>
        <TouchableOpacity 
          style={styles.linkButton}
          onPress={() => openExternalLink('https://github.com')}
        >
          <Text style={styles.buttonText}>Open GitHub (New Tab)</Text>
        </TouchableOpacity>
        
        <TouchableOpacity 
          style={styles.linkButton}
          onPress={() => openInSameTab('https://react-native-web.js.org')}
        >
          <Text style={styles.buttonText}>Open RNW Docs (Same Tab)</Text>
        </TouchableOpacity>
        
        <TouchableOpacity 
          style={styles.linkButton}
          onPress={() => openEmail('hello@example.com', 'Hello!', 'This is a test email.')}
        >
          <Text style={styles.buttonText}>Send Email</Text>
        </TouchableOpacity>
        
        <TouchableOpacity 
          style={styles.linkButton}
          onPress={() => openPhone('+1234567890')}
        >
          <Text style={styles.buttonText}>Call Phone</Text>
        </TouchableOpacity>
      </View>
    </View>
  );
}

// Advanced URL handling
class URLHandler {
  static parseURL(url) {
    try {
      const urlObj = new URL(url);
      return {
        protocol: urlObj.protocol,
        host: urlObj.host,
        pathname: urlObj.pathname,
        search: urlObj.search,
        hash: urlObj.hash,
        searchParams: Object.fromEntries(urlObj.searchParams)
      };
    } catch (error) {
      console.error('Invalid URL:', error);
      return null;
    }
  }

  static buildURL(base, params = {}) {
    const url = new URL(base);
    Object.entries(params).forEach(([key, value]) => {
      url.searchParams.set(key, value);
    });
    return url.toString();
  }

  static async openWithConfirmation(url, message = 'Open this link?') {
    if (window.confirm(message)) {
      return Linking.openURL(url, '_blank');
    }
  }

  static trackLinkClick(url, analytics = {}) {
    // Track the link click
    if (analytics.track) {
      analytics.track('External Link Clicked', { url });
    }
    return Linking.openURL(url, '_blank');
  }
}

Share

Web Share API integration for sharing content with native browser sharing capabilities and fallback implementations.

const Share: {
  share: (content: ShareContent, options?: ShareOptions) => Promise<ShareResult>;
  sharedAction: string;
  dismissedAction: string;
};

share()

Share content using the Web Share API or fallback methods.

Share.share(
  content: {
    title?: string;
    message?: string;
    url?: string;
  },
  options?: {}
): Promise<{ action: string }>

Usage:

import { Share } from "react-native-web";

function ShareDemo() {
  const [shareResult, setShareResult] = React.useState('');

  const shareContent = async (content) => {
    try {
      const result = await Share.share(content);
      
      if (result.action === Share.sharedAction) {
        setShareResult('Content shared successfully!');
      } else if (result.action === Share.dismissedAction) {
        setShareResult('Share dialog dismissed');
      }
    } catch (error) {
      console.error('Share failed:', error);
      setShareResult('Share not supported or failed');
      
      // Fallback: copy to clipboard
      if (content.url) {
        navigator.clipboard?.writeText(content.url);
        setShareResult('URL copied to clipboard as fallback');
      }
    }
  };

  const shareCurrentPage = () => {
    shareContent({
      title: document.title,
      message: 'Check out this page!',
      url: window.location.href
    });
  };

  const shareCustomContent = () => {
    shareContent({
      title: 'React Native Web',
      message: 'Build native web apps with React Native!',
      url: 'https://necolas.github.io/react-native-web/'
    });
  };

  const shareTextOnly = () => {
    shareContent({
      message: 'This is a text-only share with no URL'
    });
  };

  return (
    <View style={styles.container}>
      <Text style={styles.title}>Share Demo</Text>
      
      <View style={styles.buttonContainer}>
        <TouchableOpacity style={styles.shareButton} onPress={shareCurrentPage}>
          <Text style={styles.buttonText}>Share Current Page</Text>
        </TouchableOpacity>
        
        <TouchableOpacity style={styles.shareButton} onPress={shareCustomContent}>
          <Text style={styles.buttonText}>Share Custom Content</Text>
        </TouchableOpacity>
        
        <TouchableOpacity style={styles.shareButton} onPress={shareTextOnly}>
          <Text style={styles.buttonText}>Share Text Only</Text>
        </TouchableOpacity>
      </View>
      
      {shareResult ? (
        <Text style={styles.result}>{shareResult}</Text>
      ) : null}
    </View>
  );
}

// Enhanced sharing with multiple fallbacks
class EnhancedShare {
  static async share(content, options = {}) {
    // Try Web Share API first
    if (navigator.share) {
      try {
        await navigator.share(content);
        return { action: 'shared' };
      } catch (error) {
        if (error.name === 'AbortError') {
          return { action: 'dismissed' };
        }
        // Fall through to other methods
      }
    }

    // Fallback: Social media sharing
    return this.fallbackShare(content, options);
  }

  static async fallbackShare(content, options) {
    const { title, message, url } = content;
    const shareText = `${title ? title + ': ' : ''}${message || ''}${url ? ' ' + url : ''}`;

    if (options.preferredMethod === 'clipboard') {
      return this.shareViaClipboard(shareText);
    }

    // Show custom share modal
    return this.showShareModal(content);
  }

  static async shareViaClipboard(text) {
    try {
      await navigator.clipboard.writeText(text);
      alert('Content copied to clipboard!');
      return { action: 'shared' };
    } catch (error) {
      console.error('Clipboard share failed:', error);
      return { action: 'failed' };
    }
  }

  static showShareModal(content) {
    return new Promise((resolve) => {
      // Create custom share modal
      const modal = document.createElement('div');
      modal.innerHTML = `
        <div style="position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.5); z-index: 1000; display: flex; align-items: center; justify-content: center;">
          <div style="background: white; border-radius: 12px; padding: 20px; max-width: 400px; margin: 20px;">
            <h3>Share Content</h3>
            <p>${content.title || ''}</p>
            <p>${content.message || ''}</p>
            <div style="display: flex; gap: 10px; margin-top: 20px;">
              <button onclick="window.shareViaTwitter('${content.url}', '${content.message}')">Twitter</button>
              <button onclick="window.shareViaFacebook('${content.url}')">Facebook</button>
              <button onclick="window.copyToClipboard('${content.url || content.message}')">Copy</button>
              <button onclick="window.closeShareModal()">Cancel</button>
            </div>
          </div>
        </div>
      `;
      
      document.body.appendChild(modal);
      
      // Add global functions
      window.closeShareModal = () => {
        document.body.removeChild(modal);
        resolve({ action: 'dismissed' });
      };
      
      window.shareViaTwitter = (url, text) => {
        window.open(`https://twitter.com/intent/tweet?url=${encodeURIComponent(url)}&text=${encodeURIComponent(text)}`);
        window.closeShareModal();
        resolve({ action: 'shared' });
      };
      
      window.shareViaFacebook = (url) => {
        window.open(`https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(url)}`);
        window.closeShareModal();
        resolve({ action: 'shared' });
      };
      
      window.copyToClipboard = async (text) => {
        await navigator.clipboard.writeText(text);
        window.closeShareModal();
        resolve({ action: 'shared' });
      };
    });
  }
}

Vibration

Vibration API wrapper for web with support for patterns and fallback behavior on non-supporting devices.

const Vibration: {
  cancel: () => void;
  vibrate: (pattern?: number | number[]) => void;
};

vibrate()

Trigger device vibration with optional pattern support.

Vibration.vibrate(pattern?: number | number[] = 400): void

cancel()

Cancel any ongoing vibration.

Vibration.cancel(): void

Usage:

import { Vibration } from "react-native-web";

function VibrationDemo() {
  const [isVibrationSupported, setIsVibrationSupported] = React.useState(false);

  React.useEffect(() => {
    // Check if vibration is supported
    setIsVibrationSupported('vibrate' in navigator);
  }, []);

  const singleVibration = () => {
    Vibration.vibrate(200);
  };

  const patternVibration = () => {
    // Pattern: [vibrate, pause, vibrate, pause, ...]
    Vibration.vibrate([100, 200, 100, 200, 300]);
  };

  const longVibration = () => {
    Vibration.vibrate(1000);
  };

  const cancelVibration = () => {
    Vibration.cancel();
  };

  // Haptic feedback patterns
  const hapticPatterns = {
    notification: [50, 50, 50],
    warning: [100, 100, 100, 100, 200],
    error: [200, 100, 200, 100, 200],
    success: [50, 50, 100],
    click: [10],
    doubleClick: [10, 50, 10]
  };

  const playHaptic = (pattern) => {
    if (isVibrationSupported) {
      Vibration.vibrate(hapticPatterns[pattern]);
    } else {
      console.log(`Haptic feedback: ${pattern}`);
    }
  };

  return (
    <View style={styles.container}>
      <Text style={styles.title}>Vibration Demo</Text>
      
      <Text style={styles.status}>
        Vibration Support: {isVibrationSupported ? 'Supported' : 'Not Supported'}
      </Text>
      
      <View style={styles.buttonContainer}>
        <TouchableOpacity 
          style={styles.vibrationButton}
          onPress={singleVibration}
        >
          <Text style={styles.buttonText}>Single Vibration</Text>
        </TouchableOpacity>
        
        <TouchableOpacity 
          style={styles.vibrationButton}
          onPress={patternVibration}
        >
          <Text style={styles.buttonText}>Pattern Vibration</Text>
        </TouchableOpacity>
        
        <TouchableOpacity 
          style={styles.vibrationButton}
          onPress={longVibration}
        >
          <Text style={styles.buttonText}>Long Vibration</Text>
        </TouchableOpacity>
        
        <TouchableOpacity 
          style={[styles.vibrationButton, styles.cancelButton]}
          onPress={cancelVibration}
        >
          <Text style={styles.buttonText}>Cancel Vibration</Text>
        </TouchableOpacity>
      </View>
      
      <Text style={styles.sectionTitle}>Haptic Feedback Patterns</Text>
      <View style={styles.hapticContainer}>
        {Object.keys(hapticPatterns).map((pattern) => (
          <TouchableOpacity
            key={pattern}
            style={styles.hapticButton}
            onPress={() => playHaptic(pattern)}
          >
            <Text style={styles.hapticText}>{pattern}</Text>
          </TouchableOpacity>
        ))}
      </View>
    </View>
  );
}

// Enhanced vibration with game-like haptics
class GameHaptics {
  static isSupported() {
    return 'vibrate' in navigator;
  }

  static feedback = {
    // UI Interactions
    buttonPress: () => Vibration.vibrate(10),
    buttonRelease: () => Vibration.vibrate(5),
    
    // Game Events
    powerUp: () => Vibration.vibrate([50, 50, 100, 50, 150]),
    levelComplete: () => Vibration.vibrate([100, 100, 100, 100, 200]),
    gameOver: () => Vibration.vibrate([200, 100, 200, 100, 400]),
    
    // Notifications
    message: () => Vibration.vibrate([80, 80, 80]),
    alert: () => Vibration.vibrate([100, 200, 100, 200, 300]),
    
    // Continuous effects
    startEngine: () => {
      const pattern = Array(10).fill([20, 30]).flat();
      Vibration.vibrate(pattern);
    },
    
    stopEngine: () => Vibration.cancel()
  };

  static playSequence(sequence, callback) {
    let index = 0;
    
    const playNext = () => {
      if (index < sequence.length) {
        this.feedback[sequence[index]]();
        index++;
        setTimeout(playNext, 500); // Delay between haptics
      } else if (callback) {
        callback();
      }
    };
    
    playNext();
  }
}

I18nManager

Internationalization manager for handling right-to-left (RTL) layouts and locale-specific formatting with web-compatible implementation.

const I18nManager: {
  allowRTL: (allowRTL: boolean) => void;
  forceRTL: (forceRTL: boolean) => void;
  getConstants: () => { isRTL: boolean };
  isRTL?: boolean;
};

allowRTL()

Allow RTL layout (web implementation is no-op).

I18nManager.allowRTL(allowRTL: boolean): void

forceRTL()

Force RTL layout (web implementation is no-op).

I18nManager.forceRTL(forceRTL: boolean): void

getConstants()

Get internationalization constants including RTL status.

I18nManager.getConstants(): { isRTL: boolean }

Usage:

import { I18nManager } from "react-native-web";

function I18nDemo() {
  const [isRTL, setIsRTL] = React.useState(false);

  React.useEffect(() => {
    const constants = I18nManager.getConstants();
    setIsRTL(constants.isRTL);
  }, []);

  // Web-specific RTL detection
  const detectRTLFromDOM = () => {
    const direction = document.dir || document.documentElement.dir || 'ltr';
    return direction === 'rtl';
  };

  const detectRTLFromLanguage = (language) => {
    const rtlLanguages = ['ar', 'he', 'fa', 'ur', 'yi'];
    return rtlLanguages.includes(language.split('-')[0]);
  };

  return (
    <View style={[styles.container, isRTL && styles.rtlContainer]}>
      <Text style={styles.title}>
        Internationalization Demo
      </Text>
      
      <Text>RTL Status: {isRTL ? 'Right-to-Left' : 'Left-to-Right'}</Text>
      <Text>DOM Direction: {detectRTLFromDOM() ? 'RTL' : 'LTR'}</Text>
      
      <View style={[styles.textContainer, isRTL && styles.rtlText]}>
        <Text style={styles.label}>Name:</Text>
        <Text style={styles.value}>John Doe</Text>
      </View>
      
      <View style={[styles.textContainer, isRTL && styles.rtlText]}>
        <Text style={styles.label}>العربية:</Text>
        <Text style={styles.value}>مرحبا بالعالم</Text>
      </View>
    </View>
  );
}

// Enhanced I18n utilities for web
class WebI18nManager {
  static detectDirection(locale) {
    const rtlLocales = [
      'ar', 'arc', 'dv', 'fa', 'ha', 'he', 'khw', 'ks',
      'ku', 'ps', 'ur', 'yi'
    ];
    
    const language = locale.split('-')[0];
    return rtlLocales.includes(language) ? 'rtl' : 'ltr';
  }

  static setDocumentDirection(direction) {
    document.documentElement.dir = direction;
    document.documentElement.setAttribute('data-direction', direction);
  }

  static createRTLStyles(styles, isRTL) {
    if (!isRTL) return styles;
    
    const rtlStyles = { ...styles };
    
    // Flip horizontal margins and padding
    if (styles.marginLeft !== undefined) {
      rtlStyles.marginRight = styles.marginLeft;
      rtlStyles.marginLeft = styles.marginRight || 0;
    }
    
    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;
  }

  static useRTLLayout(locale) {
    const [isRTL, setIsRTL] = React.useState(false);
    
    React.useEffect(() => {
      const direction = this.detectDirection(locale);
      const rtl = direction === 'rtl';
      
      setIsRTL(rtl);
      this.setDocumentDirection(direction);
    }, [locale]);
    
    return isRTL;
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 16
  },
  rtlContainer: {
    flexDirection: 'row-reverse'
  },
  title: {
    fontSize: 18,
    fontWeight: 'bold',
    marginBottom: 16
  },
  textContainer: {
    flexDirection: 'row',
    marginBottom: 8,
    alignItems: 'center'
  },
  rtlText: {
    flexDirection: 'row-reverse'
  },
  label: {
    fontWeight: 'bold',
    marginRight: 8
  },
  value: {
    flex: 1
  }
});

Types

interface AppParameters {
  rootTag: Element;
  initialProps?: Object;
  hydrate?: boolean;
  mode?: 'legacy' | 'concurrent';
  callback?: () => void;
}

interface AppConfig {
  appKey: string;
  component?: () => React.ComponentType;
  run?: Function;
  section?: boolean;
}

type ComponentProviderInstrumentationHook = (component: () => React.ComponentType) => React.ComponentType;
type WrapperComponentProvider = (any) => React.ComponentType;

type ColorSchemeName = 'light' | 'dark';

interface AppearancePreferences {
  colorScheme: ColorSchemeName;
}

interface ShareContent {
  title?: string;
  message?: string;
  url?: string;
}

interface ShareOptions {}

interface ShareResult {
  action: string;
}

type VibratePattern = number | number[];

interface I18nConstants {
  isRTL: boolean;
}

type AlertButton = {
  text: string;
  onPress?: () => void;
  style?: 'default' | 'cancel' | 'destructive';
};

type AlertOptions = {
  cancelable?: boolean;
  onDismiss?: () => void;
};

interface LinkingEventCallback {
  (event: { url: string }): void;
}

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