Expoexpo-widgetsWidgetsLive ActivitiesiOS

Expo Widgets Guide: Home Screen Widgets and Live Activities in React Native

Build iOS home screen widgets, Android widgets, and iOS Live Activities in Expo with expo-widgets — config plugin setup, the 'widget' directive, Expo UI layouts, updateSnapshot and updateTimeline, and starting and updating a Live Activity.

DeveloperMill4 min read

Widgets are one of the best retention features in mobile. A habit tracker on the home screen, a delivery ETA in the Dynamic Island, a daily quote on the lock screen: each one gives users a reason to come back without opening your app. For years, adding widgets to a React Native app meant writing Swift in Xcode. expo-widgets changes that. You write widgets in React with Expo UI components, and a config plugin generates the native extension.

What you can build

  • iOS home screen widgets in small, medium, and large sizes
  • iOS Live Activities on the lock screen and in the Dynamic Island
  • Android home screen widgets (opt-in, new in Expo SDK 58). Widgets run their own JavaScript bundle on a dedicated Hermes runtime.

Install

npx expo install expo-widgets @expo/ui

Widgets need a native extension, so they don't work in Expo Go. Use a development build.

1. Register the widget in app.json

{
  "expo": {
    "plugins": [
      [
        "expo-widgets",
        {
          "widgets": [
            {
              "name": "StreakWidget",
              "displayName": "Streak",
              "description": "See your current streak at a glance",
              "ios": { "supportedFamilies": ["systemSmall", "systemMedium"] }
            }
          ]
        }
      ]
    ]
  }
}

name must match the name you pass to createWidget. displayName and description appear in the system widget gallery.

2. Write the widget in React

Widget layouts use Expo UI components (SwiftUI under the hood on iOS). The component must start with the 'widget' directive:

// widgets/StreakWidget.tsx
import { Text, VStack } from "@expo/ui/swift-ui"
import { createWidget, type WidgetEnvironment } from "expo-widgets"

type StreakProps = { days: number; doneToday: boolean }

const StreakWidget = (props: StreakProps, environment: WidgetEnvironment) => {
  "widget"
  return (
    <VStack>
      <Text>🔥 {props.days} day streak</Text>
      <Text>{props.doneToday ? "Done for today" : "Not done yet"}</Text>
    </VStack>
  )
}

export default createWidget("StreakWidget", StreakWidget, { days: 0, doneToday: false })

The third argument is the default props shown before your app sends any data.

3. Update the widget from your app

Whenever data changes, push new props. updateSnapshot updates immediately:

import StreakWidget from "@/widgets/StreakWidget"

export function onHabitCompleted(streak: number) {
  StreakWidget.updateSnapshot({ days: streak, doneToday: true })
}

updateTimeline schedules future states, so the widget changes even when the app isn't running:

const midnight = new Date()
midnight.setHours(24, 0, 0, 0)

StreakWidget.updateTimeline([
  { date: new Date(), props: { days: 12, doneToday: true } },
  { date: midnight, props: { days: 12, doneToday: false } },
])

Timelines are perfect for countdowns, daily content, and "reset at midnight" states.

4. Live Activities

Live Activities show real-time progress on the lock screen and Dynamic Island. Define one with createLiveActivity. Live Activities don't get a widgets[] entry in app.json:

// widgets/DeliveryActivity.tsx
import { Text, VStack } from "@expo/ui/swift-ui"
import { createLiveActivity } from "expo-widgets"

type DeliveryProps = { etaMinutes: number; status: string }

const DeliveryLayout = (props: DeliveryProps) => {
  "widget"
  return (
    <VStack>
      <Text>{props.status}</Text>
      <Text>{props.etaMinutes} min</Text>
    </VStack>
  )
}

export default createLiveActivity("DeliveryActivity", DeliveryLayout)

Start and update it from your app:

import DeliveryActivity from "@/widgets/DeliveryActivity"

const activity = DeliveryActivity.start(
  { etaMinutes: 15, status: "Your order is on the way" },
  "myapp://orders/12345", // opens when the user taps the activity
)

await activity.update({ etaMinutes: 2, status: "Arriving now" })

The URL is a deep link into your app. See our Expo Router deep linking guide to route it to the right screen.

Android widgets

Android widgets are opt-in: enable Android in the plugin config (enableAndroid) and add the android options to each widget entry. They run in their own Hermes runtime, separate from your app's JavaScript, so keep widget code small and self-contained. Live Activities stay iOS-only.

Widget design tips

  • One job per widget. Show the single number or action that matters.
  • Design for glanceability. Large text, high contrast, and no dense lists.
  • Deep link every tap to the relevant screen, not the home screen.
  • Update on meaningful events, not on a timer. iOS budgets widget reloads.
  • Support dark mode and tinted home screens. Use system colors where possible.

Ideas that work well as widgets

Habit and streak trackers (pair with our Scribble Calendar), to-do counts, countdowns, fitness rings, delivery and ride tracking, and "word of the day" style content. For in-app celebration when a streak is hit, add Expo Confetti and a success haptic.

Frequently asked questions

Can you build home screen widgets with Expo?

Yes. The expo-widgets package lets you write home screen widgets in React using Expo UI components. It handles the native widget extension through a config plugin, so you don't have to open Xcode. iOS widgets and Live Activities are supported, and SDK 58 added opt-in Android widgets.

Do Expo widgets work in Expo Go?

No. Widgets require a native extension, so expo-widgets is not available in Expo Go. Use a development build (`npx expo run:ios` or `eas build --profile development`).

How do I update a widget from my React Native app?

Call `updateSnapshot(props)` on the widget to change its content immediately, or `updateTimeline(entries)` to schedule a series of future states, each with a date and props. Call them whenever relevant data changes in the app — for example after a task is completed.

What are Live Activities?

Live Activities are iOS lock screen and Dynamic Island cards that show real-time progress — a delivery ETA, a ride, a workout, or a game score. With expo-widgets you define one using createLiveActivity, then call start() and update() from your app.

Build it faster with a template

Production-ready Expo React Native templates with the patterns from this post already wired up.

Browse templates