This is documentation for the next SDK version. For up-to-date documentation, see the latest version (SDK 54).
Expo Widgets
A library to build 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
Prerequisite: Registering widget layout
Widget components must be registered once using registerWidgetLayout and marked with the 'widget' directive.
import { Text, VStack } from '@expo/ui/swift-ui'; import { font, foregroundStyle } from '@expo/ui/swift-ui/modifiers'; import { registerWidgetLayout, WidgetBase } from 'expo-widgets'; type MyWidgetProps = { count: number; }; const MyWidget = (props: WidgetBase<MyWidgetProps>) => { 'widget'; return ( <VStack> <Text modifiers={[font({ weight: 'bold', size: 16 }), foregroundStyle('#000000')]}> Count: {props.count} </Text> </VStack> ); }; registerWidgetLayout('MyWidget', MyWidget);
The widget name ('MyWidget') must match the name field in your widget configuration in the app config.
Basic widget
The simplest way to create a widget is to update it using updateWidgetSnapshot. This creates a widget with a single timeline entry that displays immediately.
The example below continues from Registering widget layout.
import { updateWidgetSnapshot } from 'expo-widgets'; // Update the widget updateWidgetSnapshot('MyWidget', { count: 5 });
Timeline widget
Use updateWidgetTimeline to schedule widget updates at specific time. System will automatically update the widget based on the timeline.
The example below continues from Registering widget layout.
import { updateWidgetTimeline } from 'expo-widgets'; updateWidgetTimeline('MyWidget', [ { date: new Date(), props: { count: 1 } }, { date: new Date(Date.now() + 3600000), props: { count: 2 } }, // 1 hour from now { date: new Date(Date.now() + 7200000), props: { count: 3 } }, // 2 hours from now { date: new Date(Date.now() + 10800000), props: { count: 4 } }, // 3 hours from now ]);
Responsive widget
Widget component receives a family prop indicating which size is being rendered. Use this to adapt layout for different widget sizes.
import { HStack, Text, VStack } from '@expo/ui/swift-ui'; import { registerWidgetLayout, updateWidgetSnapshot, WidgetBase } from 'expo-widgets'; type WeatherWidgetProps = { temperature: number; condition: string; }; const WeatherWidget = (props: WidgetBase<WeatherWidgetProps>) => { 'widget'; // 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> ); }; registerWidgetLayout('WeatherWidget', WeatherWidget); updateWidgetSnapshot('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.
Prerequisite: Registering Live Activity layout
Live Activity layouts must be registered once using registerLiveActivityLayout and marked with the 'widget' directive.
import { Image, Text, VStack } from '@expo/ui/swift-ui'; import { font, foregroundStyle, padding } from '@expo/ui/swift-ui/modifiers'; import { registerLiveActivityLayout } from 'expo-widgets'; type DeliveryActivityProps = { etaMinutes: number; status: string; }; const DeliveryActivity = (props: DeliveryActivityProps) => { 'widget'; return { banner: ( <VStack modifiers={[padding({ all: 12 })]}> <Text modifiers={[font({ weight: 'bold' }), foregroundStyle('#007AFF')]}> {props.status} </Text> <Text>Estimated arrival: {props.etaMinutes} minutes</Text> </VStack> ), compactLeading: <Image systemName="box.truck.fill" color="#007AFF" />, compactTrailing: <Text>{props.etaMinutes} min</Text>, minimal: <Image systemName="box.truck.fill" color="#007AFF" />, expandedLeading: ( <VStack modifiers={[padding({ all: 12 })]}> <Image systemName="box.truck.fill" color="#007AFF" /> <Text modifiers={[font({ size: 12 })]}>Delivering</Text> </VStack> ), expandedTrailing: ( <VStack modifiers={[padding({ all: 12 })]}> <Text modifiers={[font({ weight: 'bold', size: 20 })]}>{props.etaMinutes}</Text> <Text modifiers={[font({ size: 12 })]}>minutes</Text> </VStack> ), expandedBottom: ( <VStack modifiers={[padding({ all: 12 })]}> <Text>Driver: John Smith</Text> <Text>Order #12345</Text> </VStack> ), }; }; registerLiveActivityLayout('DeliveryActivity', DeliveryActivity);
Starting a Live Activity
The example below continues from Registering Live Activity layout.
import { Image, Text, VStack } from '@expo/ui/swift-ui'; import { font, foregroundStyle, padding } from '@expo/ui/swift-ui/modifiers'; import { startLiveActivity } from 'expo-widgets'; import { Button, View } from 'react-native'; function App() { const startDeliveryTracking = () => { // Start the Live Activity const activityId = startLiveActivity('DeliveryActivity', { etaMinutes: 15, status: 'Your delivery is on the way', }); console.log('Started Live Activity:', activityId); // Store activityId for later updates }; return ( <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}> <Button title="Start Delivery Tracking" onPress={startDeliveryTracking} /> </View> ); } export default App;
Updating a Live Activity
The example below continues from Starting a Live Activity.
import { updateLiveActivity } from 'expo-widgets'; function updateDelivery(activityId: string) { updateLiveActivity(activityId, 'DeliveryActivity', { etaMinutes: 2, status: 'Delivery arriving soon!', }); }
API
import * as ExpoWidgets from 'expo-widgets';
No API data file found, sorry!