Reference version

Expo Widgets

A library that enables creation of iOS home screen widgets and Live Activities using Expo UI components.

iOS

This library is currently in alpha and subject to breaking changes. It is not available in the Expo Go app, use development builds to try it out.

expo-widgets enables the creation of iOS home screen widgets and Live Activities using Expo UI components, without writing native code. It provides a simple API for creating and updating widgets timeline, as well as starting and managing Live Activities. Layout can be built using expo/ui components and modifiers.

Installation

Terminal
- npx expo install expo-widgets

If you are installing this in an existing React Native app, make sure to install expo in your project.

Configuration in app config

You can configure expo-widgets using its built-in config plugin if you use config plugins in your project (Continuous Native Generation (CNG)). The plugin allows you to configure various properties that cannot be set at runtime and require building a new app binary to take effect.

Example app.json with config plugin

app.json
{ "expo": { "plugins": [ [ "expo-widgets", { "widgets": [ { "name": "MyWidget", "displayName": "My Widget", "description": "A sample home screen widget", "supportedFamilies": ["systemSmall", "systemMedium", "systemLarge"] } ] } ] ] } }

Configurable properties

NameDefaultDescription
bundleIdentifier"<app bundle identifier>.ExpoWidgetsTarget"

The bundle identifier for the widget extension target. If not specified, defaults to <main app bundle identifier>.ExpoWidgetsTarget.

groupIdentifier"group.<app bundle identifier>"

The app group identifier used for communication and data sharing between the main app and widgets. This is required for widgets to work properly. If not specified, defaults to group.<main app bundle identifier>.

enablePushNotificationsfalse

Whether to enable push notifications for Live Activities. When enabled, this adds the aps-environment entitlement and sets ExpoLiveActivity_EnablePushNotifications in the Info.plist.

widgets-

An array of widget configurations. Each widget in the array will be generated as a separate widget kind in your widget extension.

widgets[].name-

The internal name (identifier) of the widget. This is used as the Swift struct name and should be a valid Swift identifier (no spaces or special characters).

widgets[].displayName-

The user-facing name of the widget that appears in the widget gallery when users add widgets to their home screen.

widgets[].description-

A brief description of what the widget does. This appears in the widget gallery to help users understand the widget's purpose.

widgets[].supportedFamilies-

An array of widget sizes that this widget supports. Available options:

  • systemSmall - Small square widget (2x2 grid)
  • systemMedium - Medium rectangular widget (4x2 grid)
  • systemLarge - Large square widget (4x4 grid)
  • systemExtraLarge - Extra large widget (iPad only, 6x4 grid)
  • accessoryCircular - Circular widget for Lock Screen
  • accessoryRectangular - Rectangular widget for Lock Screen
  • accessoryInline - Inline text widget for Lock Screen

Full example with all options

app.json
{ "expo": { "plugins": [ [ "expo-widgets", { "bundleIdentifier": "com.example.myapp.widgets", "groupIdentifier": "group.com.example.myapp", "enablePushNotifications": true, "widgets": [ { "name": "StatusWidget", "displayName": "Status", "description": "Shows your current status at a glance", "supportedFamilies": ["systemSmall", "systemMedium"] }, { "name": "DetailWidget", "displayName": "Details", "description": "Shows detailed information", "supportedFamilies": ["systemMedium", "systemLarge"] }, { "name": "LockScreenWidget", "displayName": "Quick View", "description": "View info on your Lock Screen", "supportedFamilies": ["accessoryCircular", "accessoryRectangular", "accessoryInline"] } ] } ] ] } }

Usage

Widgets

Basic widget

The simplest way to create a widget is using updateWidgetSnapshot. This creates a widget with a single timeline entry that displays immediately.

import { updateWidgetSnapshot, WidgetBase } from 'expo-widgets'; import { Text, VStack } from '@expo/ui/swift-ui'; import { font, foregroundStyle } from '@expo/ui/swift-ui/modifiers'; type MyWidgetProps = { count: number; }; const MyWidget = (props: WidgetBase<MyWidgetProps>) => { return ( <VStack> <Text modifiers={[font({ weight: 'bold', size: 16 }), foregroundStyle('#000000')]}> Count: {props.count} </Text> </VStack> ); }; // Update the widget updateWidgetSnapshot('MyWidget', MyWidget, { count: 5 });

The widget name ('MyWidget') must match the name field in your widget configuration in the app config.

Timeline widget

Use updateWidgetTimeline to schedule widget updates at specific time. System will automatically update the widget based on the timeline.

import { updateWidgetTimeline, WidgetProps } from 'expo-widgets'; import { Text, VStack } from '@expo/ui/swift-ui'; import { font } from '@expo/ui/swift-ui/modifiers'; const TimeWidget = (props: WidgetProps) => { return ( <VStack> <Text>{props.date.toLocaleTimeString()}</Text> </VStack> ); }; // Schedule updates for the next 3 hours const dates = [ new Date(), new Date(Date.now() + 3600000), // 1 hour from now new Date(Date.now() + 7200000), // 2 hours from now new Date(Date.now() + 10800000), // 3 hours from now ]; updateWidgetTimeline('TimeWidget', dates, TimeWidget);

Responsive widget

Widget component receives a family prop indicating which size is being rendered. Use this to adapt layout for different widget sizes.

import { updateWidgetSnapshot, WidgetProps, WidgetFamily } from 'expo-widgets'; import { Text, VStack, HStack } from '@expo/ui/swift-ui'; type WeatherWidgetProps = { temperature: number; condition: string; }; const WeatherWidget = (props: WidgetProps<WeatherWidgetProps>) => { // Render different layouts based on size if (props.family === 'systemSmall') { return ( <VStack> <Text>{props.temperature}°</Text> </VStack> ); } if (props.family === 'systemMedium') { return ( <HStack> <Text>{props.temperature}°</Text> <Text>{props.condition}</Text> </HStack> ); } // systemLarge and others return ( <VStack> <Text>Temperature: {props.temperature}°</Text> <Text>Condition: {props.condition}</Text> <Text>Updated: {props.date.toLocaleTimeString()}</Text> </VStack> ); }; updateWidgetSnapshot('WeatherWidget', WeatherWidget, { temperature: 72, condition: 'Sunny', });

Live Activities

Live Activities display real-time information on the Lock Screen and in the Dynamic Island on supported devices.

Starting a Live Activity

import { startLiveActivity, LiveActivityComponent } from 'expo-widgets'; import { Text, Image, HStack, VStack } from '@expo/ui/swift-ui'; import { foregroundStyle, font, padding } from '@expo/ui/swift-ui/modifiers'; import { Button } from 'react-native'; const DeliveryActivity: LiveActivityComponent = () => ({ banner: ( <VStack modifiers={[padding(12)]}> <Text modifiers={[font({ weight: 'bold' }), foregroundStyle('#000000')]}> Your delivery is on the way </Text> <Text>Estimated arrival: 15 minutes</Text> </VStack> ), compactLeading: <Image systemName="box.truck.fill" color="#007AFF" />, compactTrailing: <Text>15 min</Text>, minimal: <Image systemName="box.truck.fill" color="#007AFF" />, expandedLeading: ( <VStack modifiers={[padding(12)]}> <Image systemName="box.truck.fill" color="#007AFF" /> <Text modifiers={[font({ size: 12 })]}>Delivering</Text> </VStack> ), expandedTrailing: ( <VStack modifiers={[padding(12)]}> <Text modifiers={[font({ weight: 'bold', size: 20 })]}>15</Text> <Text modifiers={[font({ size: 12 })]}>minutes</Text> </VStack> ), expandedBottom: ( <VStack modifiers={[padding(12)]}> <Text>Driver: John Smith</Text> <Text>Order #12345</Text> </VStack> ), }); function App() { const startDeliveryTracking = () => { // Start the Live Activity const activityId = startLiveActivity('DeliveryActivity', DeliveryActivity); console.log('Started Live Activity:', activityId); // Store activityId for later updates }; return ( <View> <Button title="Start Delivery Tracking" onPress={startDeliveryTracking} /> </View> ); }

Updating a Live Activity

import { updateLiveActivity, LiveActivityComponent } from 'expo-widgets'; const UpdatedDeliveryActivity: LiveActivityComponent = () => ({ banner: ( <VStack modifiers={[padding(12)]}> <Text modifiers={[font({ weight: 'bold' }), foregroundStyle('#00FF00')]}> Delivery arriving soon! </Text> <Text>Estimated arrival: 2 minutes</Text> </VStack> ), compactLeading: <Image systemName="checkmark.circle.fill" color="#00FF00" />, compactTrailing: <Text>2 min</Text>, expandedTrailing: ( <VStack modifiers={[padding(12)]}> <Text modifiers={[font({ weight: 'bold', size: 20 })]}>2</Text> <Text modifiers={[font({ size: 12 })]}>minutes</Text> </VStack> ), }); function updateDelivery(activityId: string) { updateLiveActivity(activityId, 'DeliveryActivity', UpdatedDeliveryActivity); }

API

import { updateWidgetSnapshot, updateWidgetTimeline, WidgetBase, WidgetFamily, updateLiveActivity, LiveActivityComponent } from 'expo-widgets';

Methods

startLiveActivity(name, liveActivity, url)

iOS
ParameterTypeDescription
namestring

The name/identifier of the Live Activity to start.

liveActivityLiveActivityComponent

A function that returns the Live Activity layout configuration.

url(optional)string

An optional deep link URL to open when the user taps the Live Activity.


Starts a new Live Activity on iOS. Live Activities display real-time information on the Lock Screen and in the Dynamic Island.

Returns:
string

The unique identifier of the started Live Activity.

updateLiveActivity(id, name, liveActivity)

iOS
ParameterTypeDescription
idstring

The unique identifier of the Live Activity to update (returned from startLiveActivity).

namestring

The name/identifier of the Live Activity.

liveActivityLiveActivityComponent

A function that returns the updated Live Activity layout configuration.


Updates an existing Live Activity with new content.

Returns:
void

updateWidgetSnapshot(name, widget, props, updateFunction)

iOS
ParameterTypeDescription
namestring

The name/identifier of the widget to update.

widget(p: WidgetBase<T>) => Element

A function component that renders the widget content for a given set of props.

props(optional)T

Optional custom props to pass to the widget component.

updateFunction(optional)string

Optional name of a function to call for dynamic updates.


Updates a widget with a single snapshot entry for the current time. This is a convenience wrapper around updateWidgetTimeline for widgets that don't need multiple timeline entries.

Returns:
void

updateWidgetTimeline(name, dates, widget, props, updateFunction)

iOS
ParameterTypeDescription
namestring

The name/identifier of the widget to update.

datesDate[]

An array of dates representing when each timeline entry should be displayed.

widget(p: WidgetBase<T>) => Element

A function component that renders the widget content for a given set of props.

props(optional)T

Optional custom props to pass to the widget component.

updateFunction(optional)string

Optional name of a function to call for dynamic updates.


Updates a widget's timeline with multiple entries that will be displayed at scheduled times. The widget system will automatically switch between entries based on their timestamps.

Returns:
void

Event Subscriptions

addUserInteractionListener(listener)

iOS
ParameterTypeDescription
listener(event: UserInteractionEvent) => void

Callback function to handle user interaction events.


Adds a listener for widget interaction events (for example, button taps).

Returns:
EventSubscription

An event subscription that can be used to remove the listener.

Types

ExpoLiveActivityEntry

iOS

Defines the layout sections for an iOS Live Activity.

PropertyTypeDescription
bannerReactNode

The main banner content displayed in Notifications Center.

bannerSmall(optional)ReactNode

The small banner content displayed in CarPlay and WatchOS. Falls back to banner if not provided.

compactLeading(optional)ReactNode

The leading content in the compact Dynamic Island presentation.

compactTrailing(optional)ReactNode

The trailing content in the compact Dynamic Island presentation.

expandedBottom(optional)ReactNode

The bottom content in the expanded Dynamic Island presentation.

expandedCenter(optional)ReactNode

The center content in the expanded Dynamic Island presentation.

expandedLeading(optional)ReactNode

The leading content in the expanded Dynamic Island presentation.

expandedTrailing(optional)ReactNode

The trailing content in the expanded Dynamic Island presentation.

minimal(optional)ReactNode

The minimal content shown when the Dynamic Island is in its smallest form.

ExpoWidgetsEvents

iOS
PropertyTypeDescription
onUserInteraction(event: UserInteractionEvent) => void

Function that is invoked when user interacts with a widget.

event: UserInteractionEvent

Interaction event details.

LiveActivityComponent()

iOS

A function that returns the layout for a Live Activity.

UserInteractionEvent

iOS

Event emitted when a user interacts with a widget.

PropertyTypeDescription
sourcestring

Widget that triggered the interaction.

targetstring

Button/toggle that was pressed.

timestampnumber

Timestamp of the event.

type'ExpoWidgetsUserInteraction'

The event type identifier.

WidgetBase

iOS

Props passed to a widget component.

Type: T extended by:

PropertyTypeDescription
dateDate

The date of this timeline entry.

familyWidgetFamily

The widget family.

WidgetFamily

iOS

Literal Type: string

The widget family (size).

  • systemSmall - Small square widget (2x2 grid).
  • systemMedium - Medium widget (4x2 grid).
  • systemLarge - Large widget (4x4 grid).
  • systemExtraLarge - Extra large widget (iPad only, 6x4 grid).
  • accessoryCircular - Circular accessory widget for the Lock Screen.
  • accessoryRectangular - Rectangular accessory widget for the Lock Screen.
  • accessoryInline - Inline accessory widget for the Lock Screen.

Acceptable values are: 'systemSmall' | 'systemMedium' | 'systemLarge' | 'systemExtraLarge' | 'accessoryCircular' | 'accessoryRectangular' | 'accessoryInline'