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.
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
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:
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:
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 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.
- 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.
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.
Portrait and landscape use per-device landscape safe areas. On foldables, the fold or hinge is drawn and rotates with the device.
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.
- 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 (
localStorageon 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.
npx expo install react-native-device-simulator react-native-svg react-native-safe-area-contextreact-native-svg is required. react-native-safe-area-context is optional but
recommended. Expo Router and React Navigation already use it.
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>
);
}npx expo start --webWeb 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.
Some things can't be simulated from JavaScript alone:
- The native
SafeAreaViewfromreact-native-safe-area-contextmeasures the real screen. - The OS applies text scaling and bold text.
- On web, react-native-web's
KeyboardAvoidingViewdoes 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.
| 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 |
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.
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();| 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.
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.
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 pickerA preset without frame artwork gets a generic frame for its kind.
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(orhtml-to-image). - Native: install
react-native-view-shot.expo-sharingis optional.
- Web: install the optional
simulationInfoPlugin()shows every simulated value, with a "Copy JSON" button.
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.
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.
- 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.OSis 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
SafeAreaViewneed the Metro helper. - Mouse input on web: drag-to-scroll is not emulated. Use the wheel or trackpad.
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).
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.
MIT. The device specs and artwork are © 2019 Aloïs Deniel, also MIT.









