Skip to content

About

Preview your React Native app on any device - iPhone, iPad, Android, foldables, desktop - with real frames, safe areas, dark mode, text scale, locales and accessibility. A port of Flutter's device_preview.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

react-native-device-simulator

npm version license: MIT

Preview your React Native app on 33 real devices, from one screen. Wrap your app once, pick an iPhone, iPad, Pixel, Galaxy foldable or desktop window, and the app runs inside that device's frame. It also gets the device's screen size, pixel ratio, safe areas, keyboard, color scheme, text size, locale and accessibility settings, and you don't change any app code.

The example app running inside an iPhone 17 Pro frame, with the device toolbar open on the right

A React Native port of Flutter's device_preview. It works with Expo and bare React Native, on web, iOS and Android.

Quick start · Features · API · How it works · Limitations · Credits

Features

33 devices with real frames

iPhone 16 and 17 family, iPhone Air, iPhone SE, iPad (mini, Air, Pro), Pixel 9 and 10, Pixel 10 Pro Fold, Galaxy S24 and S25, Galaxy Z Flip8 and Z Fold8, Galaxy Tabs, and small and large desktop windows. Frames include the Dynamic Island, notches, punch-hole cameras, rounded screen corners, a status bar and a home indicator or navigation bar. Devices are shown at their real logical size relative to each other:

The example app on an iPad Pro 11-inch in landscape, a Pixel 10 Pro Fold, an iPhone 17 Pro, a Pixel 10 and an iPhone SE

You can switch devices and orientation while the app is running. The app is never remounted, so navigation state, form input and scroll position all stay where they were:

Switching between iPhone SE, Pixel 10, Galaxy Z Flip8, Pixel 10 Pro Fold, iPad Pro in landscape and a desktop window using the toolbar

Your app sees the simulated device

React Native's own APIs report the simulated device, so React Navigation, Expo Router and your own responsive code lay out the same way they would on that device:

API Reports
useWindowDimensions, Dimensions Screen size in the current orientation
PixelRatio The device's pixel ratio
useColorScheme, Appearance The simulated light or dark mode
AccessibilityInfo Simulated accessibility settings and their change events
Keyboard Show, hide and frame-change events for the virtual keyboard, in the same order as iOS
react-native-safe-area-context The device's safe area insets (portrait and landscape)

The example app's Inspector screen prints those values live. In this screenshot it runs on an iPad mini with bold text, reduce motion and screen reader turned on:

The Inspector screen on an iPad mini, showing window size, pixel ratio, safe area insets and accessibility values, next to the Accessibility panel

A virtual keyboard that behaves like the real one

The keyboard comes in iOS and Gboard styles, and it is sized for each device and orientation. It sends real Keyboard events, so KeyboardAvoidingView and keyboard-aware layouts react to it the same way they do on a device.

A booking form with the iOS keyboard on an iPhone 17 Pro and the Gboard keyboard on a Pixel 10 in dark mode

Appearance, text size and locales

  • Light and dark mode.
  • Text scale from 0.5x to 3.5x, and bold text. These are applied through the Metro helper.
  • Locales. Right-to-left languages such as Arabic, Hebrew, Persian and Urdu flip the layout direction of the simulated screen.

The Discover screen on an iPhone 17 in light mode and in dark mode

The World screen on a Pixel 10 in Arabic (ar-EG), right-to-left, with 1.5x bold text, next to the Display panel

Accessibility

You can override reduce motion, reduce transparency, invert colors, grayscale, high contrast, darker system colors, screen reader and bold text. Each one has three states: use the device setting, on, or off.

Foldables and orientation

Portrait and landscape use per-device landscape safe areas. On foldables, the fold or hinge is drawn and rotates with the device.

The Discover screen on a Pixel 10 Pro Fold in landscape with the dark toolbar

Custom devices

The Custom device editor lets you set any size, pixel ratio, platform, safe area and keyboard height. You can also register your own presets in code.

A custom 800 by 1280 Android tablet called ACME Kiosk, with the Inspector reporting its size and pixel ratio, next to the custom device editor

Also included

  • Screenshots of the device with its frame, or the screen only, at the device's real resolution (screenshotPlugin()).
  • Plugins that add your own tabs to the toolbar.
  • Programmatic control for tests, demos and dev menus (deviceSimulator.setDevice(...)).
  • Persisted settings (localStorage on web, any AsyncStorage-like store on native).
  • Off in release builds by default (enabled={__DEV__}): nothing is patched or rendered.
  • A compact toolbar that moves to the bottom on narrow windows, so you can even use the simulator from a phone-sized browser window.

The simulator in a phone-sized browser window, with the toolbar collapsed to a bar at the bottom

Quick start

1. Install

npx expo install react-native-device-simulator react-native-svg react-native-safe-area-context

react-native-svg is required. react-native-safe-area-context is optional but recommended. Expo Router and React Navigation already use it.

2. Wrap your app

Expo Router. Wrap the navigator in your root layout:

// app/_layout.tsx
import { Stack } from 'expo-router';
import { DeviceSimulator } from 'react-native-device-simulator';

export default function RootLayout() {
  return (
    <DeviceSimulator>
      <Stack />
    </DeviceSimulator>
  );
}

Bare React Native, or Expo without Router:

import { SafeAreaProvider } from 'react-native-safe-area-context';
import { DeviceSimulator } from 'react-native-device-simulator';

export default function App() {
  return (
    <SafeAreaProvider>
      <DeviceSimulator>
        <Navigation />
      </DeviceSimulator>
    </SafeAreaProvider>
  );
}

3. Run it

npx expo start --web

Web is the fastest way to check many layouts. The toolbar is on the right, or at the bottom on small windows. It also runs inside your iOS or Android app, for example on an iPad simulator showing phone layouts.

Recommended: the Metro helper

Some things can't be simulated from JavaScript alone:

  • The native SafeAreaView from react-native-safe-area-context measures the real screen.
  • The OS applies text scaling and bold text.
  • On web, react-native-web's KeyboardAvoidingView does nothing.

withDeviceSimulator redirects those modules to simulator-aware versions. When no device is being simulated, they behave exactly like the originals:

// metro.config.js
const { getDefaultConfig } = require('expo/metro-config');
const { withDeviceSimulator } = require('react-native-device-simulator/metro');

module.exports = withDeviceSimulator(getDefaultConfig(__dirname));

Options: withDeviceSimulator(config, { safeArea, textScaling, keyboardAvoidingView }). All of them default to true.

API

<DeviceSimulator> props

Prop Default Description
enabled __DEV__ When false, renders children only and patches nothing.
devices all built-ins Devices offered by the picker.
defaultSettings Initial settings, e.g. { deviceId: 'google-pixel-10', colorScheme: 'dark' }.
storage localStorage on web Where settings persist. Pass AsyncStorage on native, or null to disable.
plugins [] Extra toolbar tabs, e.g. [screenshotPlugin(), simulationInfoPlugin()].
padding 24 Space around the device.
maxScale 1 Allows drawing the device larger than its logical size.
import AsyncStorage from '@react-native-async-storage/async-storage';
import { DeviceSimulator, Devices, screenshotPlugin, simulationInfoPlugin } from 'react-native-device-simulator';

<DeviceSimulator
  storage={AsyncStorage}
  devices={[Devices.iPhone17Pro, Devices.iPhoneSE3rdGen, Devices.pixel10, Devices.iPadAir11M4]}
  defaultSettings={{ deviceId: Devices.pixel10.id }}
  plugins={[screenshotPlugin(), simulationInfoPlugin()]}
>
  <App />
</DeviceSimulator>
Built-in devices (Devices.*)
Brand Devices
Apple iPhone iPhone17Pro, iPhone17ProMax, iPhone17, iPhone17e, iPhoneAir, iPhone16Pro, iPhone16ProMax, iPhone16, iPhone16Plus, iPhoneSE3rdGen
Apple iPad iPadPro11M5, iPadPro13M5, iPadPro11M4, iPadPro13M4, iPadAir11M4, iPadAir13M4, iPadAir11M2, iPadAir13M2, iPadMiniA17Pro, iPadA16, iPad10thGen
Google pixel10, pixel10ProFold, pixel9
Samsung galaxyS25, galaxyS24, galaxyZFlip8, galaxyZFold8, galaxyZFold8Ultra, galaxyTabS11, galaxyTabS10Plus
Desktop smallDesktopWindow, largeDesktopWindow

allPresets contains all of them. Each preset's id is the file name of its spec in device-specs/, e.g. apple-iphone-17-pro.

Programmatic control

deviceSimulator is a global controller, the equivalent of Flutter's DevicePreview.controller. You can use it anywhere, including outside React:

import { deviceSimulator, Devices } from 'react-native-device-simulator';

deviceSimulator.setDevice(Devices.iPhoneSE3rdGen);
deviceSimulator.setOrientation('landscape');
deviceSimulator.setColorScheme('dark');
deviceSimulator.setFontScale(2);
deviceSimulator.setLocale('he-IL');
deviceSimulator.setKeyboardVisible(true);
deviceSimulator.update({ accessibility: { ...deviceSimulator.getSettings().accessibility, reduceMotion: true } });
deviceSimulator.setEnabled(false); // show the app full-screen, state kept
deviceSimulator.reset();

Hooks

Hook Returns
useDeviceSimulator() { settings, simulation, isActive, controller }
useDeviceSimulatorSettings() What the user chose (DeviceSimulatorSettings)
useDeviceSimulatorSimulation() What the app sees: screen size, insets, pixel ratio, keyboard height, overrides
useDeviceSimulatorActive() Whether a device is being simulated right now
useDeviceSimulatorLocale() The simulated locale (BCP-47), or null

getRealDimensions() and getRealColorScheme() return the host's real values when you need them.

Locales

Apps choose their language in many different ways, so the library exposes the simulated locale and leaves it to you to apply. Pass it to your i18n library:

import { useDeviceSimulatorLocale } from 'react-native-device-simulator';

function I18nSync() {
  const simulatedLocale = useDeviceSimulatorLocale(); // e.g. 'ar-EG', or null
  useEffect(() => {
    i18n.changeLanguage(simulatedLocale ?? getLocales()[0].languageTag);
  }, [simulatedLocale]);
  return null;
}

Right-to-left locales flip the layout direction of the simulated screen automatically.

Custom devices

Any device in the device spec format works:

import { deviceSimulator, type DevicePreset } from 'react-native-device-simulator';

const kioskTablet: DevicePreset = {
  id: 'acme-kiosk',
  name: 'ACME Kiosk',
  brand: 'ACME',
  platform: 'android',
  kind: 'tablet',
  portraitSize: { width: 800, height: 1280 },
  devicePixelRatio: 1.5,
  portraitPadding: { top: 24, right: 0, bottom: 0, left: 0 },
};

deviceSimulator.registerPreset(kioskTablet); // appears in the picker

A preset without frame artwork gets a generic frame for its kind.

Plugins

A plugin adds a tab to the toolbar:

import type { DeviceSimulatorPlugin } from 'react-native-device-simulator';

const featureFlags: DeviceSimulatorPlugin = {
  id: 'flags',
  title: 'Flags',
  icon: '⚑',
  render: ({ theme }) => <FlagsPanel color={theme.text} />,
};

render receives { controller, settings, simulation, theme }.

Built-in plugins:

  • screenshotPlugin() captures the device or the screen.
    • Web: install the optional modern-screenshot (or html-to-image).
    • Native: install react-native-view-shot. expo-sharing is optional.
  • simulationInfoPlugin() shows every simulated value, with a "Copy JSON" button.

How it works

The library renders the app in a view the size of the simulated screen, then scales that view to fit the window. It wraps the JavaScript modules behind React Native's globals (Dimensions, Appearance, AccessibilityInfo, Keyboard) so they report the simulated device and notify subscribers when it changes. Safe area insets come through react-native-safe-area-context's contexts. The device frames are SVG and come from device_preview's device specs. For details, see docs/ARCHITECTURE.md.

Platforms

The library runs on web (react-native-web), iOS and Android, with Expo or bare React Native. It has been checked on Expo web, on an iPad simulator, and in a large React Native 0.86 app (New Architecture, Hermes, @react-native/babel-preset) on the iOS simulator and an Android emulator. Reports from other setups are welcome.

Limitations

  • The native look is not simulated. An iOS host renders iOS-native components (switches, pickers, alerts) even when previewing an Android device, because native components can't change platform at runtime. Platform.OS is deliberately not faked, because libraries depend on it to pick native modules.
  • Native modals (Modal, native-stack modals, alerts) are presented by the OS over the whole window, outside the device frame.
  • Native animations that read OS settings directly (e.g. Reanimated's useReducedMotion) see the host setting.
  • Text scaling and the native SafeAreaView need the Metro helper.
  • Mouse input on web: drag-to-scroll is not emulated. Use the wheel or trackpad.

Contributing

Issues and pull requests are welcome. The repository is an npm workspace:

packages/react-native-device-simulator/   the library
example/                                   Expo Router showcase app
device-specs/                              device JSON specs (from device_preview, MIT)
scripts/generate-presets.mjs               device-specs -> generated presets
docs/ARCHITECTURE.md                       design notes and invariants
npm install
cd example && npx expo start --web            # run the example app
cd packages/react-native-device-simulator
npx tsc -p . && npx jest                      # typecheck and tests
node scripts/generate-presets.mjs             # after editing device-specs/

The example app has five screens that exercise the library: Home, Discover (a responsive grid), Book (a form with the keyboard), World (locales, RTL and the clock) and Inspector (live values).

Credits

This project wouldn't exist without device_preview by Aloïs Deniel. The idea, the user experience and the device catalog come from his work. The device metrics, frame artwork and system UI artwork in device-specs/ are taken from that project under the MIT license (see device-specs/LICENSE). If you also build with Flutter, use the original device_preview.

This project is an independent React Native port. It is not affiliated with or endorsed by the original authors.

License

MIT. The device specs and artwork are © 2019 Aloïs Deniel, also MIT.

About

Preview your React Native app on any device - iPhone, iPad, Android, foldables, desktop - with real frames, safe areas, dark mode, text scale, locales and accessibility. A port of Flutter's device_preview.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages