React Native 0.88 - Web-Based Event Dispatching, Variable Fonts, SceneDelegate, no breaking changes
Today we are excited to release React Native 0.88!
This release switches event dispatching to the Web-based path by default, brings variable fonts to Text and TextInput on both platforms and adds SceneDelegate lifecycle support on iOS. It also bumps the React version to 19.3.0 and moves to Metro 0.87.
Following 0.83 and 0.86, this is the third React Native release with no user-facing breaking changes, reflecting our continued commitment to making upgrades more predictable and seamless.
Highlights
- Web-based event dispatching
- Variable fonts with fontVariationSettings
- SceneDelegate lifecycle support on iOS
- ArrayBuffer for Java TurboModules
- Experimental Swift Package Manager support
Web-based event dispatching
React Native now dispatches events through the W3C EventTarget API by default, replacing the plugin-based system the renderer has used since its early days. Unblocked by the react@19.3.0 bump in this release, it is the largest runtime change in 0.88.
Events now travel a real capture path down to the target and bubble back up, over nodes that are genuine event targets. The payoff:
- The same model as the web: Propagation,
capture,stopPropagation()andpreventDefault()are no longer a React Native reimplementation of the DOM. Libraries that reason about event flow can increasingly be written once, against one model. - A path to Web-compatible imperative APIs: Canary builds expose
addEventListener,removeEventListeneranddispatchEventon native refs for early feedback. - One path to maintain: Removing the legacy plugin registry is the follow-up, leaving a single dispatch path instead of two.
The imperative API is available on the Canary channel in 0.88:
const ref = useRef(null);
useEffect(() => {
const onClick = event => {
// ...
};
ref.current?.addEventListener('click', onClick);
return () => ref.current?.removeEventListener('click', onClick);
}, []);
On Canary, if an element has both a declarative prop and an imperative listener for the same event, the prop runs first.
What this means for your app
Nothing should change. Your event props keep the same names, receive the same synthetic event objects (nativeEvent, persist() and the rest) and run in the same order. Direct events such as onLayout still fire only on the target. Errors still reach React error boundaries synchronously. The touch responder system is unchanged.
The code to test is anything reaching past the public API into React Native’s event internals, since the plugin registry it was written against is no longer the path events take. The legacy path is still present in 0.88 behind an internal flag, but it is expected to be removed with the follow-up, so please report anything that only works the old way.
Variable fonts with fontVariationSettings
fontVariationSettings is now supported on both <Text> and <TextInput>, on Android and iOS. Both the CSS string form and an object form are accepted:
<Text style={{fontVariationSettings: {wght: 700, slnt: -10}}}>Object form</Text>
<Text style={{fontVariationSettings: "'wght' 700, 'slnt' -10"}}>String form</Text>
Object keys are four-character axis tags with finite numeric values.
ArrayBuffer for Java TurboModules
Java TurboModules can now take and return JavaScript ArrayBuffer values directly. Codegen maps them to com.facebook.react.bridge.ArrayBuffer, so binary data no longer needs to be encoded as strings or number arrays:
export interface Spec extends TurboModule {
- process(data: string): string; // for example, Base64
+ process(data: ArrayBuffer): ArrayBuffer;
}
For synchronous methods, inputs can borrow JavaScript memory and avoid a copy. Those borrowed bytes are valid only until the method returns. If you need to retain them, copy them during the call:
override fun keepForLater(data: ArrayBuffer) {
retained = ArrayBuffer.arrayBufferWithCopiedBytes(data)
}
Accessing ArrayBuffer.bytes or ArrayBuffer.size after a borrowed buffer has been revoked throws an IllegalStateException.
iOS: SceneDelegate support
Starting with the iOS 27 SDK, Apple requires the UIScene lifecycle: UIKit apps built with Xcode 27 will fail to launch.
React Native 0.88 introduces support for this lifecycle as an additive change, keeping backwards compatibility with the AppDelegate path. RCTReactNativeFactory gains two new SceneDelegate entry points:
startReactNativeWithModuleName:inWindow:connectionOptions:startReactNativeWithModuleName:inWindow:initialProperties:connectionOptions:
Support in 0.88 is limited to a single scene, so keep UIApplicationSupportsMultipleScenes set to false. At launch, React Native forwards only the first URL context or user activity from the scene’s connection options.
To migrate, you need to:
- move the React Native entry point from your
AppDelegateto aSceneDelegate - forward the scene’s URL and user-activity callbacks to
RCTLinkingManager - add a
UIApplicationSceneManifestkey toInfo.plist.
The community template has been updated, so newly created apps already use the new entry point. You can use the community template or the upgrade helper, as an example to migrate your app, specifically the SceneDelegate.swift and Info.plist files.
With the UIScene lifecycle, the app window is resizable. Dimensions.get() and useWindowDimensions() update with the current values.
Experimental: Swift Package Manager support
Swift Package Manager support remains experimental and opt-in in React Native 0.88, while CocoaPods remains fully supported. Since the preview in 0.87, we have hardened the tooling using feedback from real apps and libraries.
Our current rollout plan is:
- 0.87: Introduced experimental Swift Package Manager support.
- 0.88: Hardened the scripts to support more libraries and edge cases.
- 0.89: Target Swift Package Manager as the default, with an opt-out to CocoaPods for apps that are still blocked.
We plan to remove CocoaPods support in a later release of React Native.
Please try the preview and report blockers in RFC #0994.
Please refer to the blogpost for React Native 0.87 for instructions on how to opt-in.
Swift Package Manager Improvements
The npx react-native spm tooling shipped in 0.87, has been improved in 0.88 with fixes from real apps and libraries, so SwiftPM projects build more reliably in Xcode and on the command line.
List of improvements
- iOS version is read from the app: Generated manifests now take their minimum iOS version from the app's deployment target, not a hardcoded iOS 15. Packages that need a newer iOS version, such as Expo's, can now be autolinked
- Stable Xcode builds: Xcode builds no longer break for libraries that ship their own
Package.swift - Portable projects: Apps no longer commit a machine-specific Hermes compiler path
- Bash for build scripts: Generated build scripts now run under bash
- Configuration in package.json: Libraries can now declare their SwiftPM settings in
package.json’sswiftpmConfigsection. The Swift Package Manager block inreact-native.config.jsis deprecated - Names from the podspec: When a library declares no SwiftPM name, the scaffolder takes it from the podspec.
- Mirror-first artifact downloads: Prebuilt artifacts now download from the React Native Maven mirror first, with Maven Central as fallback.
Breaking Changes
React Native 0.88 has no user-facing breaking changes.
If you are on React Native 0.87, you should be able to upgrade your app to React Native 0.88 without any changes to your app code.
To learn more about what we consider a breaking change, have a look at this article.
Deprecations
The following APIs have been deprecated and are due for removal in a future release of React Native:
InitializeCore:'react-native/Libraries/Core/InitializeCore'is deprecated. Use'react-native/setup-env'instead.- Prefixed background styles:
experimental_backgroundSize,experimental_backgroundPositionandexperimental_backgroundRepeatare deprecated in favour of their unprefixed names. The prefixed names still work as aliases. InputAccessoryView: iOS only, deprecated as part of keeping core lean.react-native-keyboard-controller’sKeyboardStickyViewis a cross-platform replacement. Nothing changes in 0.88 beyond a JSDoc tag and a one-time warning on import.ReactFragment.fabricEnabled: the New Architecture is unconditionally enabled on this path, so the per-fragment override no longer does anything. Drop the argument and the matching builder call.ReactImageManagerString?overloads:setDefaultSourceandsetLoadingIndicatorSourcenow take aDynamic. TheString?versions still work but break under props 2.0, so pass the prop value through rather than pre-resolving it.
Other Changes
Runtime & Web Spec Alignment
-
Styles:
backgroundSize,backgroundPositionandbackgroundRepeatlose theirexperimental_prefix and are now stable. The prefixed names still work. -
Runtime:
RuntimeSchedulerclears pending tasks and rendering updates when a task throws. -
Runtime: async
CallInvokerwork is buffered alongside callable module calls, so it no longer runs before the JS bundle is ready. -
FileReader: fires
loadstart, throwsInvalidStateErroron overlapping reads and exposesFileReader.erroras aDOMException, matching the web spec (544141efab). -
FileReader: stays in the correct state after a read is aborted (e92816c5a5).
-
Blob: a
Blobis retained while a native read is pending, so it is no longer collected mid-read (568eaa6b69). -
Blob: fixed an intermittent iOS crash when deriving blob names for network responses (671705dfb1).
-
i18n: RTL is detected from the active locale rather than an arbitrary installed one (e46189e1e1).
Rendering, Layout & Animation
-
iOS RTL text: text now aligns right by default in RTL when
textAlignis not set, matching Android (1b92b02f99). -
Radial gradients: circle gradients now reject percentage radii, matching web behaviour (5821fcaf5d).
-
Animated:
addListenerfires again for natively driven values derived withadd,subtract,multiply,divide,modulo,diffClamporinterpolate. -
ScrollView:
maintainVisibleContentPositionholds position through rapid data updates (5cb65244dc). -
ScrollView: programmatic scrolls no longer cancel active touches in enclosing scroll views (06eb1fefab).
-
ScrollView: recycled scroll views no longer keep the previous automatic keyboard inset behaviour (e0aa7a8b0a).
-
Image:
getSize()andgetSizeWithHeaders()acceptdata:URIs (8cfde6d083). -
Image: source headers survive
crossOriginandreferrerPolicy(08c7781e5a). -
Image: fixed a crash when
borderRadiusis a percentage (f2a250ad89). -
Image:
aria-*state props are no longer ignored whenaccessibilityStateis also set (c3caea9b5e). -
Renderer: fixed an
IllegalStateExceptioninSurfaceMountingManager.removeViewAtand view registry entries being lost (3f553d76bc, d6a5f159c6). -
Renderer: recycled iOS views clear
UIView.hiddencorrectly (711a9a5e07). -
Renderer: missing native component registrations are logged on both platforms (ef28a2c9d5).
TypeScript & JavaScript API
- Module augmentation: Additional generated props and style types can now be extended via module augmentation, widening compatibility with styling libraries.
- Added declarations for
requestIdleCallback/cancelIdleCallback,LogBox.isInstalled()/clearAllLogs(),FlatList'sstrictModeprop andVirtualizedList'sListItemComponent.
Infrastructure & Dependencies
- React 19.3.0 is synced into React Native.
- Metro moves to 0.87, with the minimum bumped to 0.87.1 so upgrading projects resolve the same Metro as freshly created ones.
- Hermes moves to 260318099.0.4.
iOS
- Assets: images can be served from an asset catalog by setting
RCTUseAssetCatalogin the app'sInfo.plist. It is off by default. - CocoaPods: a
React-cxxstableapidependency is added to third-party New Architecture pods so they can resolve React Native's C++ API. - Codegen: no longer crawls
node_modulesor follows symlinks when discovering components.
Android
- Networking: the default
User-Agentnow includes the app name and version, matching iOS (04e50d540d). A server keying off the old default will see a different value. - Renderer:
androidx.collectionmoves to 1.4.4, fixing view registry entries getting lost inSurfaceMountingManager. - Network Inspection: Network event reporting is exposed to third-party networking stacks as an unstable API.
Developer Tools
- React Native DevTools: App screenshots via
Page.captureScreenshotare available by default. - React Native DevTools: WebSocket inspection in the Network panel is on the CANARY channel.
Acknowledgements
React Native 0.88 contains 568 commits from 91 contributors. Thanks for all your hard work!
We want to send a special thank you to those community members that shipped significant contributions in this release.
- Artur Morys-Magiera for SceneDelegate support
- Jakub Piasecki for the C++ Stable API work, guarding the renderer’s internal surfaces and marking what frameworks may rely on
- Christian Falch for Swift Package Manager hardening
- Dawid Malecki for faster, more reliable dependency downloads from mirror and proxy caches
- Mathieu Acthernoene for making generated props and style types augmentable
- Rob Hogan for improvements to Metro
Upgrade to 0.88
0.88 is now the latest stable version of React Native and 0.85.x moves to unsupported. For more information see React Native's support policy.
Upgrading
Please use the React Native Upgrade Helper to view code changes between React Native versions for existing projects, in addition to the Upgrading docs.
Create a new project
npx @react-native-community/cli@latest init MyProject --version latest
Expo
For Expo projects, React Native 0.88 will be available as part of the Expo SDK 58 releases.

