Expo Widgets
A library that enables creation of iOS home screen widgets and Live Activities using Expo UI components.
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
-Â npx expo install expo-widgetsIf 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
{ "expo": { "plugins": [ [ "expo-widgets", { "widgets": [ { "name": "MyWidget", "displayName": "My Widget", "description": "A sample home screen widget", "supportedFamilies": ["systemSmall", "systemMedium", "systemLarge"] } ] } ] ] } }
Configurable properties
| Name | Default | Description |
|---|---|---|
bundleIdentifier | "<app bundle identifier>.ExpoWidgetsTarget" | The bundle identifier for the widget extension target. If not specified, defaults to |
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 |
enablePushNotifications | false | Whether to enable push notifications for Live Activities. When enabled, this adds the |
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:
|
Full example with all options
{ "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
| Parameter | Type | Description |
|---|---|---|
| name | string | The name/identifier of the Live Activity to start. |
| liveActivity | LiveActivityComponent | 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.
stringThe unique identifier of the started Live Activity.
| Parameter | Type | Description |
|---|---|---|
| id | string | The unique identifier of the Live Activity to update (returned from |
| name | string | The name/identifier of the Live Activity. |
| liveActivity | LiveActivityComponent | A function that returns the updated Live Activity layout configuration. |
Updates an existing Live Activity with new content.
void| Parameter | Type | Description |
|---|---|---|
| name | string | 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.
void| Parameter | Type | Description |
|---|---|---|
| name | string | The name/identifier of the widget to update. |
| dates | Date[] | 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.
voidEvent Subscriptions
| Parameter | Type | Description |
|---|---|---|
| listener | (event: UserInteractionEvent) => void | Callback function to handle user interaction events. |
Adds a listener for widget interaction events (for example, button taps).
EventSubscriptionAn event subscription that can be used to remove the listener.
Types
Defines the layout sections for an iOS Live Activity.
| Property | Type | Description |
|---|---|---|
| banner | ReactNode | The main banner content displayed in Notifications Center. |
| bannerSmall(optional) | ReactNode | The small banner content displayed in CarPlay and WatchOS. Falls back to |
| 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. |
| Property | Type | Description |
|---|---|---|
| onUserInteraction | (event: UserInteractionEvent) => void | Function that is invoked when user interacts with a widget. event: UserInteractionEventInteraction event details. |
A function that returns the layout for a Live Activity.
Event emitted when a user interacts with a widget.
| Property | Type | Description |
|---|---|---|
| source | string | Widget that triggered the interaction. |
| target | string | Button/toggle that was pressed. |
| timestamp | number | Timestamp of the event. |
| type | 'ExpoWidgetsUserInteraction' | The event type identifier. |
Props passed to a widget component.
Type: T extended by:
| Property | Type | Description |
|---|---|---|
| date | Date | The date of this timeline entry. |
| family | WidgetFamily | The widget family. |
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'