Widgets and Live Activities are perfect for the moments when opening the app is more work than the user should have to do. A delivery ETA, a workout timer, a flight status, the next meeting, a saved shortcut - this is the kind of information people want while they are already doing something else. Widgets and live activities allow your apps to stay useful, without even asking users to open it.
Building those surfaces from a React Native app has historically meant a separate Xcode target, App Groups, SwiftUI layout code, and the ongoing burden of keeping that extension in sync with the rest of your app.
Starting with SDK 56, expo-widgets is stable. You build widgets and Live Activities as React components using Expo UI. Continuous Native Generation handles the Widget Extension target, the App Group configuration, and the SwiftUI scaffolding. There's no separate iOS project to maintain.
This is the same library that shipped as alpha in SDK 55, hardened and promoted. The core idea hasn't changed: write widgets in React, let the system render them through SwiftUI, and manage updates from your Expo app. What changed in SDK 56 is the stability promise and a set of practical improvements: widgets and Live Activities now receive their environment directly, they no longer need to be pre-rendered by the main app, and error handling is better.
Why widgets and Live Activities are worth building
The reason to build widgets and Live Activities isn't UI completeness. It's surface area.
The app icon is one tap into your app. A widget is zero taps and can provide value while the user is doing something else. A Live Activity stays present during an in-progress event and updates as conditions change, even when the device is locked. These surfaces complement the app by keeping relevant information close by when a full app session is not needed.
Flighty is a good public example. It won the 2023 Apple Design Award for Interaction, and Apple's writeup calls out the parts that matter here: Live Activities, Dynamic Island integration, widget support, and putting key flight information where travelers need it most. For a flight tracker, that is the product experience. Travelers want gate changes, boarding status, and arrival timing without repeatedly opening an app. Flighty puts that information on the Lock Screen and Dynamic Island so it is available when the trip is actually happening.
The pattern that wins in production:
- Widgets for information users want close at hand (calendar, weather, a habit streak, a balance),
- Live Activities for “in-progress” events with a clear start and end (a delivery ETA, a workout timer, a ride share, a flight in progress).
Both let your app stay relevant during the time when users aren't actively in the app.
OneSignal's 2024 State of Customer Engagement Report (bit old but the data is concrete) found that apps using iOS Live Activities have 23.7% higher average 30-day retention rates. It is not a promise that adding a Live Activity will automatically improve retention, but it does point to the same product pattern: timely information, shown in the right place, can make an app easier to return to.
What's stable in SDK 56?
The alpha shipped with SDK 55 in March. Since then, community testing and internal iteration surfaced the rough edges we needed to smooth out before calling the library stable. The headline changes:
Widgets and Live Activities now receive their full environment through WidgetEnvironment, with context such as widget family, content margins, color scheme, rendering mode, and presentation and accessibility context that helps widgets adapt to where and how the system is displaying them.
Widgets and Live Activities no longer require pre-rendering in the app. Instead of doing a startup pass through the app's React tree to prepare widget layout, the widget bundle can render independently with the context it needs for the current surface.
Configurable widgets. SDK 56 adds support for configurable widgets to let users choose what a widget displays from the widget gallery instead of requiring every choice to happen inside the app first.
Building your first widget
Below we have some instructions for how to build your first widget. If you would prefer to watch a video tutorial about how to build a widget check this out:
After installing the library and running prebuild, a minimal widget looks like this:
import { Button, Text, VStack } from '@expo/ui/swift-ui';import { font, foregroundStyle } from '@expo/ui/swift-ui/modifiers';import { createWidget, type WidgetEnvironment } from 'expo-widgets';type Props = { count: number };const CoffeeCounter = (props: Props, environment: WidgetEnvironment) => {'widget';return (<VStack spacing={8}><Text modifiers={[font({ size: 48 })]}>☕</Text><Text modifiers={[font({ size: 32, weight: 'bold' })]}>{props.count}</Text><Buttonmodifiers={[foregroundStyle('white')]}label="+"target="increment"onPress={() => ({ count: props.count + 1 })}/></VStack>);};export default createWidget('CoffeeCounter', CoffeeCounter);
The 'widget' directive marks the function body as a widget render context. It runs in a sandboxed JS runtime separate from your main app. The Expo UI components map directly to SwiftUI primitives, so the system renders the widget natively while your widget logic stays in TypeScript.
For Live Activities, the same model applies, but you describe multiple slots (Lock Screen banner, Dynamic Island compact, expanded, and minimal), and the system picks the right one based on context. Updates can be pushed from your server via APNs or triggered from the app directly.
Widget vs Live Activity: how to decide
The short version above is enough for most product discussions, but the implementation tradeoffs are different. Start by defining the information you want to share with the user.
For a widget, decide what information deserves a permanent place outside the app, how often it needs to change, which widget sizes are useful, and what should happen when the user taps it. The best widget usually has one clear job.
For a Live Activity, define the lifecycle first. What starts the activity? What can change while it is active? What final state should remain visible when it ends? Do updates come from the app, from your server, or both? Those questions matter because Live Activities include push tokens, server-side update logic, state transitions, and dismissal handling.
If you are considering both, a widget is often a good place to start. It has fewer moving pieces, so it is a practical way to get familiar with the rendering model. Once you are comfortable with that workflow, add a Live Activity for the part of your product that has a clear start, active updates, and an end.
Where to start with expo-widgets
- Install
expo-widgetsand follow the widgets reference to scaffold your first widget. - Pick one piece of information users benefit from seeing outside the app, and ship that widget first.
- If your app has a natural in-progress moment, prototype a Live Activity for that flow.
If you hit rough edges or have ideas for what to ship next, let us know in Discord or on GitHub. We're especially interested in production deployments and any retention or engagement data you're willing to share. Real numbers shape what we prioritize next.


