react-brain
Browse decisions
React & language foundations 3App architecture 11UI 13Platform & native 9Build, test, observe, secure 6AI in React apps 3stack composerdecision recordscensusstaleness benchnewsletterarchitecturechangelogmethodologyroadmap

entriesplatform-native · verified 2026-09-21 · react-native

Native UI extensions — Live Activities, widgets, App Clips, portals

reviewedconfidence: mediumtrack: heldthis tier holds 71% on the public scorecard (7/20 graded · 0 overturned) →

cited by: nav

re-verified 7× — 2026-09-24 · 2026-09-21 · 2026-09-10 · 2026-07-16 · 2026-07-10 · 2026-07-09 +1 · changelog

recommendation

iOS widgets / Live Activities: expo-widgets on Expo (stable since SDK 56, no native code), Voltra 2 on bare RN. Android widgets: expo-widgets on Expo SDK 58 (beta), Voltra 2.3 on bare RN. Custom native targets / App Clips / share extensions → Expo Targets. What changes the pick is Expo vs bare RN, then whether the Android side (days old on both) is production-ready for you.

  • iOS widgets / Live Activities on Expo → expo-widgets (SDK 56+, stable, no SwiftUI)
  • Live Activities on bare RN → Voltra 2 (Turbo Modules; no longer Expo-only)
  • widgets / App Clips / share extensions needing custom native → Expo Targets
  • ANDROID home-screen widgets on Expo → expo-widgets (SDK 58 beta; own JS bundle on a dedicated Hermes runtime)
  • ANDROID widgets on bare RN, or a widget needing per-instance settings or server-driven props → Voltra 2.3
  • Siri / Shortcuts / Spotlight / Apple Intelligence entry points → expo-app-intents (alpha; SDK 58 beta), else Expo Targets if you want to own the Swift
  • native-layer portals, e.g. moving a playing video or map between parents without a flash → react-native-teleport (1.2+)

Options & tradeoffs

the field considered — and why each one isn’t the default here

optiontradeoff
expo-widgets (Expo)Home-screen widgets + Live Activities from Expo UI components, no SwiftUI/native code; full env access, no pre-render. STABLE in Expo SDK 56 (alpha in SDK 55) on iOS; SDK 58 (beta) adds an Android implementation on a dedicated Hermes runtime. See note: ANDROID WIDGETS.
Voltra (Callstack)Live Activities + widgets with SwiftUI-style layout (VStack/HStack); since v2.0 (2026-06-18) on Turbo Modules, so it runs in BARE RN (v2 migration required); iOS 16.2+ (interactive 17+), iOS 18+ activity families. 2.3 (2026-09-15) adds server-driven widgets and per-instance Android config. See note: VOLTRA 2.3.
Expo TargetsNative targets (widgets, App Clips, share extensions) in Swift/Kotlin or RN; multi-step setup. The pick when the surface needs custom native code the other rows do not expose.
expo-app-intentsALPHA (Expo SDK 58 beta): exposes Apple App Intents (Siri, Shortcuts, Spotlight, Apple Intelligence) from an Expo app; `npx expo-app-intents init` scaffolds it. Docs and examples still to come, so prototype only. See note: APP INTENTS.
react-native-teleportPortals rendering in the native layer (re-parenting); web + native. 1.2.0 removes the Android black/white flash when teleporting video and maps, with no API or config to adopt. See note: TELEPORT 1.2.

Verified notes

THE PICTURE: native UI extensions (widgets, Live Activities, App Clips, App Intents) live outside the React tree, so the question is who generates the native target. On Expo, expo-widgets does it from Expo UI components with no SwiftUI; on bare RN, Voltra 2 does it over Turbo Modules; for anything custom, Expo Targets hands you the native target. The iOS side is settled; the Android side arrived on both main options in September 2026 and is days old, so prototype your platform coverage before committing a product surface to it. ANDROID WIDGETS (2026-09-21, verified vs the Expo SDK 58 beta changelog of 2026-09-15 + the Voltra 2.3.0/2.3.1 releases): expo-widgets gained an Android implementation where each widget runs its own JS bundle on a DEDICATED HERMES RUNTIME, with interactions and Material Colors → a widget is a second app-sized thing to keep fast; treat widget JS as a startup-budget question, not free reuse of app code. On iOS the same changelog adds a staleDate option on LiveActivity.start()/update(), stable ActivityKit identifiers and runtime widget configuration changes. VOLTRA 2.3 (2026-09-15, verified vs the v2.3.0 release): SERVER-DRIVEN Dynamic Widgets (a widget fetches its own props from your server on a schedule, per placement, instead of the app pushing); PER-INSTANCE Android widget configuration that survives a launcher backup/restore (setWidgetInstanceConfiguration: two placements of one widget showing different cities); experimental JSON-diff Dynamic Live Activities on iOS; an ArcProgressIndicator gauge for Android. Voltra 2.0 (2026-06-18, verified vs the v2.0.0 release) rewrote onto Turbo Modules and dropped the Expo-Modules / Dev-Client requirement. APP INTENTS (2026-09-21, verified vs the SDK 58 beta changelog): intent types are declared in Swift INLINE MODULES under an `app-intents` directory, because Apple's build-time metadata extraction only sees code in the app target; the package delivers invocations to JavaScript and stores entity values → you still write Swift for the intent declarations. EXPO UI (2026-09-21, verified vs the SDK 58 beta changelog): @expo/ui gained NavigationStack, NavigationLink, NavigationDestination, Toolbar, a navigationTitle modifier and two/three-column NavigationSplitView on iOS; Compose Image, DateRangePicker, DateRangePickerDialog and VerticalSlider on Android; ShapeStyle-accepting modifiers (material or gradient fills); and a screenshot per component in the docs. The fix most likely to matter in an existing app: views inside <RNHostView> are now MEASURED where SwiftUI/Compose placed them, which was behind dropped presses, gestures stolen by an enclosing scrollable, and missing touches inside BottomSheet, Popover, AlertDialog and DropdownMenu. The web BottomSheet dropped the unmaintained `vaul` for an in-house HTML <dialog>. EXPO UI + WORKLETS (2026-07-10, verified vs the post's og:description): Expo UI integrates react-native-worklets, so SwiftUI and Compose state can be driven SYNCHRONOUSLY on the UI thread with no JS round-trips → relevant when native-UI surfaces must track gestures or animation. TELEPORT 1.2 (verified vs the 1.2.0 blog post): the old Android move used removeView()/addView(), which detached the view from the window and disconnected the rendering surface behind a SurfaceView/TextureView: a black or white flash while the compositor waited for the next frame, worst on older devices. 1.2 reparents an attached view with detachViewFromParent()/attachViewToParent(), keeping its rendering resources alive. The fast path is used only when both parents share an Android window with no active focus/layout transition; otherwise it falls back to the old behavior. HISTORY: 2026-09-21 REOPENED: Android widgets land on both options (expo-widgets SDK 58 beta, Voltra 2.3); expo-app-intents alpha; @expo/ui navigation primitives + RNHostView measurement fix. The "iOS-leaning" verdict is half-retired: iOS settled, Android days old. 2026-07-10 REOPENED: Expo UI + worklets synchronous native state. 2026-06 REOPENED: expo-widgets stable in SDK 56; Voltra 2.0 bare-RN capable.

Canonical reading

Editorial annotations on why each piece matters — the articles themselves are the originals; read them there.

Apple Home Screen Widgets with Expo (Continuous Native Generation)Evan Bacon (Expo)

Generating native Apple widget targets from Expo via CNG — pbxproj manipulation to keep SwiftUI targets editable yet outside /ios, App Group / NSUserDefaults data sharing, CSS-to-colorset conversion. The foundational mechanism behind Expo Targets / expo-widgets.

Using Live Activities in a React Native AppMichael Hayes (Add Jam)

End-to-end Live Activities in RN — the ActivityKit Attributes/ContentState model, a separate widget-extension target for Lock Screen + Dynamic Island, a native module bridging JS↔Swift, and the HTTP-refresh vs APNs-push tradeoff. Platform constraints that outlast any single library.

How we built the v0 iOS app (React Native + Expo)Fernando Rojo (Vercel)

Production case study of making RN feel truly native on iOS: Liquid Glass via @callstack/liquid-glass, native menus via Zeego/UIMenu, patching RCTUITextView for platform-correct text behavior, ~1,000 lines of keyboard logic on react-native-keyboard-controller (see RB-E-KEYBOARD), synchronous New-Arch measurements, LegendList — and the share-types-and-logic-not-UI lesson. What 'native feel' actually costs and where the wins come from.

Watching

Curated talks, podcasts & videos — the A/V companion to the reading list.

Building v0 iOS and Fixing React Native Along the WayFernando Rojo, Szymon Rybczak & Oskar Kwaśniewski

The v0 iOS team on what shipping a native-feel RN app took — including the upstream RN fixes it produced. Video companion to the v0 case study in `reading`.

Sources

The full explanation

The reviewed long-form essay behind this entry — the why, not a how-to. Also on GitHub.

About native UI extensions — Live Activities, widgets, App Clips, portals

Diataxis: Explanation. This page builds understanding of the native-UI-extension decision — why widgets and Live Activities are unlike any other screen you ship, and how the candidate tools divide the work of producing them. It is not an ActivityKit how-to and not a widget-setup walkthrough. The candidate list and one-line tradeoffs live in the index entry RB-E-NATIVE-UI; the entry declares no depth-audit skill. Read this for the why.

The one idea that organises everything: the OS owns these pixels

A widget or Live Activity is not a screen in your app. The Add Jam reading under this entry lays out the machinery: a separate widget-extension target renders the Lock Screen and Dynamic Island surfaces; content flows through ActivityKit's Attributes/ContentState model; and updates arrive over the OS's channels — an HTTP-refresh vs APNs-push tradeoff the reading calls a platform constraint that outlasts any single library. Your React app does not paint these surfaces. Something has to produce the OS's declarative UI on your behalf — which makes the real question of this shelf: who compiles your React to the OS's declarative format?

The candidates are three different answers to that question, plus one option outside it:

  • Expo compiles it. expo-widgets builds iOS home-screen widgets and Live Activities from Expo UI components — no SwiftUI, no native code. STABLE in Expo SDK 56 (alpha in SDK 55); the entry's row adds "full env access, no pre-render" (quoted as-is; see the closing flags).
  • You write the OS's shapes in JS. Voltra's layout is SwiftUI-style (VStack/HStack) — the OS's format, authored from JS. v2.0 (2026-06-18) rewrote onto Turbo Modules, so it now runs in BARE RN (the Expo-Modules / Dev-Client requirement is gone; v2 migration required). OS gates ride along: iOS 16.2+, interactive widgets 17+, activity families iOS 18+.
  • Nobody compiles — you author the native target yourself. Expo Targets scaffolds native targets (widgets, App Clips, share extensions) in Swift/Kotlin or RN; multi-step setup. The Evan Bacon reading shows the machinery underneath both Expo paths: CNG pbxproj manipulation keeping SwiftUI targets editable yet outside /ios, App Group / NSUserDefaults data sharing, CSS-to-colorset conversion.
  • Outside the question: react-native-teleport — portals that re-parent rendering into the native layer, web + native. One line in the entry, and the who-compiles lens does not apply to it; this doc scopes it accordingly rather than inventing detail.

One scoping note: most of this story is grounded for iOS — SwiftUI targets, ActivityKit, iOS version gates. Android arrived in September 2026 on both main options (below): the iOS side is settled, the Android side is days old.

The default, and why

iOS widgets / Live Activities: expo-widgets on Expo (stable since SDK 56, no native code), Voltra 2 on bare RN. Android widgets: expo-widgets on Expo SDK 58 (beta), Voltra 2.3 on bare RN. Custom native targets / App Clips / share extensions → Expo Targets.

The split follows who can compile for you. On Expo, the platform does it — expo-widgets, no native code, stable as of SDK 56. On bare RN, Voltra 2's Turbo-Modules rewrite made it the answer it previously wasn't (it was Expo-only before v2). And when the surface needs custom native — App Clips, share extensions, widgets beyond what the compiled paths express — Expo Targets hands you the target itself, in Swift/Kotlin or RN, at the cost of multi-step setup.

The landscape, facet by facet

expo-widgets (Expo) — iOS widgets + Live Activities from Expo UI components, no SwiftUI/native code; stable in SDK 56, alpha in SDK 55 (VERIFIED 2026-06); full env access, no pre-render. The on-Expo default. SDK 58 (beta, 2026-09-15) adds an Android implementation in which each widget runs its own JS bundle on a dedicated Hermes runtime — a second app-sized thing to keep fast, so widget JS is a startup-budget question, not free reuse of app code.

Voltra (Callstack) — Live Activities + widgets with SwiftUI-style layout (VStack/HStack). The v2.0 rewrite (2026-06-18, Turbo Modules) is the capability flip of this entry: bare-RN capable, Expo Modules no longer required, v2 migration required. iOS 16.2+; interactive 17+; iOS 18+ activity families. 2.3 (2026-09-15) adds server-driven widgets (a widget fetches its own props on a schedule) and per-instance Android widget configuration that survives a launcher backup/restore — the bare-RN answer for Android widgets.

expo-app-intents — ALPHA in the SDK 58 beta: exposes Apple App Intents (Siri, Shortcuts, Spotlight, Apple Intelligence) from an Expo app. Intent types are still declared in Swift inline modules, because Apple's build-time metadata extraction only sees code in the app target. Docs are still to come: prototype only.

Expo Targets — native targets (widgets, App Clips, share extensions) in Swift/Kotlin or RN; multi-step setup. The custom-native answer, sitting on the CNG mechanism the Bacon reading documents.

react-native-teleport — portals rendering in the native layer via re-parenting; web + native. 1.2.0 fixed the visible Android flaw: reparenting an attached view instead of remove/add keeps the rendering surface alive, so teleporting video and maps no longer flashes.

Expo UI + worklets (2026-07-10, verified vs the post's og:description) — Expo UI integrates react-native-worklets: SwiftUI and Compose state can be driven SYNCHRONOUSLY on the UI thread, no JS round-trips. The entry pins its relevance: when native-UI surfaces must track gestures/animation.

The production evidence — the v0 iOS case study (Vercel) is the entry's picture of what in-app native feel costs, adjacent to the OS-owned surfaces above: Liquid Glass via @callstack/liquid-glass, native menus via Zeego/UIMenu, patching RCTUITextView, ~1,000 lines of keyboard logic on react-native-keyboard-controller (see RB-E-KEYBOARD), synchronous New-Arch measurements, LegendList — and the share-types-and-logic-not-UI lesson. The watching row is its video companion: the v0 team on the upstream RN fixes the app produced.

Tradeoffs and failure modes to name out loud

  • Assuming Voltra needs Expo. Stale since v2.0 (2026-06-18): the Turbo-Modules rewrite dropped the Expo-Modules / Dev-Client requirement. The flip side: coming from v1, the v2 migration is required, not optional.
  • Assuming expo-widgets is still alpha. Also stale — alpha in SDK 55, stable in SDK 56. Which SDK you are on decides which claim is true for you.
  • Ignoring the iOS version gates. Voltra's floor is iOS 16.2, interactivity needs 17+, activity families need iOS 18+. These are OS gates, not library choices.
  • Treating the Android side as settled. Android widgets landed on both expo-widgets (SDK 58 beta) and Voltra 2.3 in the same fortnight of September 2026; they are days old. Prototype platform coverage before committing a product surface.
  • Forgetting the update channel is a design decision. HTTP-refresh vs APNs-push is the Add Jam reading's named tradeoff, and it belongs to the platform-constraint layer that outlasts any single library.
  • Driving a live surface through JS round-trips. When a native-UI surface must track gestures/animation, the worklets integration exists precisely so SwiftUI/Compose state can be driven synchronously on the UI thread.

How it interacts with the rest of the stack

  • Keyboard (RB-E-KEYBOARD). The entry's own cross-reference: the v0 case study's ~1,000 lines of keyboard logic on react-native-keyboard-controller live in that entry's territory — keyboard behavior is a large slice of what "feels native" means.
  • Animation (RB-E-ANIMATION). react-native-worklets is the shared machinery: that entry's doc already names Expo UI synchronous state as a beyond-animation use of worklets; this entry supplies the native-UI side of the same fact.
  • Native integration (RB-E-NATIVE). The module layer these tools stand on — Voltra 2 is a Turbo-Modules rewrite, and the Add Jam reading's Live Activities bridge is a native module connecting JS to Swift.

In one paragraph

Widgets and Live Activities render in a separate widget-extension target on OS-owned surfaces (Lock Screen, Dynamic Island), fed through ActivityKit's Attributes/ContentState model and updated over HTTP-refresh or APNs-push — the OS owns these pixels, so the real question is who compiles your React to the OS's declarative format. The candidates are three answers: Expo compiles it (expo-widgets — Expo UI components, no SwiftUI, stable in SDK 56); you write the OS's shapes from JS (Voltra — SwiftUI-style VStack/HStack, bare-RN capable since the v2.0 Turbo-Modules rewrite, iOS 16.2+/17+/18+ gates); or you author the native target yourself (Expo Targets — widgets, App Clips, share extensions in Swift/Kotlin or RN, multi-step setup, on the CNG pbxproj machinery). react-native-teleport's native-layer portals sit outside that question. iOS is settled; Android widgets arrived on both expo-widgets (SDK 58 beta) and Voltra 2.3 in September 2026 and are days old, so prototype platform coverage before committing. When these surfaces must track gestures or animation, Expo UI's worklets integration drives SwiftUI and Compose state synchronously on the UI thread, no JS round-trips.


See also: RB-E-KEYBOARD (the v0 case study's keyboard-controller work — the entry's own pointer), RB-E-ANIMATION (worklets as shared machinery), RB-E-NATIVE (the Turbo-Modules / native-module layer underneath). Background reading: Evan Bacon on CNG widget generation — the foundational mechanism behind Expo Targets / expo-widgets; Add Jam on Live Activities' platform constraints; Vercel's v0 iOS case study on what native feel actually costs.

Related in platform-native: native · media · storage · keyboard · payments · brownfield · games · alt-frameworks