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.

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

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!