Widgets – Getting started with visionOS Widgets

Four key concepts that make widgets so compelling on visionOS.

Widgets on one of the best new features coming in visionOS 26. They provide a vital and previously missing level of continuity. Widgets can be useful, decorative, interactive, and just plain playful. Let’s dive in with the basics, then a handful concepts.

Widget Basics

To add a widget to an existing project, go to File > New > Target…

With the visionOS template filter selected, look for Widget Extension and add it to your project.

Adding a Widget Extension

This will add a new target to your Xcode project with some template files to get you started.

  • An AppIntents file for configuration
  • A WidgetBundle where we can add one or more widgets
  • An example file with multiple structs that make up a widget

We can switch Xcode to build this target and run it in the Simulator.

Select the widget target

Building and testing widgets in the Simulator is perfect for quick iterations. The visionOS 26 beta has a dedicated Widgets app. After running your widget, open this app and look for it in the sidebar.

StepIntoWidgets selected in the Widgets app.

From here we can tap Add Widget. This will add the current version to next to this window. A few notes about working in the Simulator.

  • The Simulator doesn’t support walls, so we can’t snap and lock them to surfaces.
  • Sometimes the widget may not update after running it. Simply remove it and re-add it.
  • Sometimes the widget preview may not update after running it. Simply select another widget, then select your widget again in the sidebar.

As of Xcode Beta 4, widgets seem to update their content anytime I run the app, but your experience may differ from mine.

We’ll add custom widget content in some future examples. For now, let’s use the “Favorite Emoji” example from the template and focus on some features.

Widget Size

We can use supportedFamilies to define an array of sizes for a widget. Not all sizes are supported on visionOS. As of visionOS Beta 4 we can use: [.systemSmall, .systemMedium, .systemExtraLargePortrait]. No one really understands why Apple excluded the large square .systemLarge from visionOS. Instead, we are encouraged to use .systemExtraLargePortrait, which is new for visionOS.

struct SimpleWidgets: Widget {
    let kind: String = "SimpleWidgets"

    var body: some WidgetConfiguration {
        AppIntentConfiguration(kind: kind, intent: ConfigurationAppIntent.self, provider: Provider()) { entry in
            SimpleWidgetsEntryView(entry: entry)
                .containerBackground(.white.gradient, for: .widget)
        }
        .supportedFamilies([.systemSmall, .systemMedium, .systemExtraLargePortrait])
    }
}

Mounting Styles

We can use .supportedMountingStyles to support .elevated (default) or .recessed (new) mounting styles. If you support both styles, then users can pick the one they want when configuring the widget. Some widgets may only make sense in one or the other style. For example, Apple uses .recessed on the Weather widget to make it feel more like a window or portal.

struct SimpleWidgets: Widget {
    let kind: String = "SimpleWidgets"

    var body: some WidgetConfiguration {
        AppIntentConfiguration(kind: kind, intent: ConfigurationAppIntent.self, provider: Provider()) { entry in
            SimpleWidgetsEntryView(entry: entry)
                .containerBackground(.white.gradient, for: .widget)
        }
        .supportedFamilies([.systemSmall, .systemMedium, .systemExtraLargePortrait])
        .supportedMountingStyles([.elevated, .recessed])
    }
}

Texture

We can use widgetTexture to select a texture that suits our design. Currently we can choose from .glass and .paper. The paper variant looks really good when viewed on device.

struct SimpleWidgets: Widget {
    let kind: String = "SimpleWidgets"

    var body: some WidgetConfiguration {
        AppIntentConfiguration(kind: kind, intent: ConfigurationAppIntent.self, provider: Provider()) { entry in
            SimpleWidgetsEntryView(entry: entry)
                .containerBackground(.white.gradient, for: .widget)
        }
        .supportedFamilies([.systemSmall, .systemMedium, .systemExtraLargePortrait])
        .supportedMountingStyles([.elevated, .recessed])
        .widgetTexture(.paper) // or .glass
    }
}

Level of Detail

Widgets on visionOS can have two versions. A detailed (default) version to display when a user is nearby and a simplified version. We don’t have to support the simplified version if we don’t need it, but it can be a nice way to reduce visual clutter and noise. Apple uses level of detail in the poster-sized music widgets. When viewed from far away, this widget shows album art, title, and artist. When we approach it, the widget shifts to the default view and shows more information such as a list of songs for the album.

To support level of detail, we can import it from the environment, the switch on the cases.

struct SimpleWidgetsEntryView : View {
    var entry: Provider.Entry
    @Environment(\.levelOfDetail) var levelOfDetail: LevelOfDetail

    var body: some View {
        switch levelOfDetail {
        case .simplified:
            VStack {
                Text(entry.date, style: .time)
                Text(entry.configuration.favoriteEmoji)
            }
        default:
            VStack {
                Text("Time:")
                Text(entry.date, style: .time)

                Text("Favorite Emoji:")
                Text(entry.configuration.favoriteEmoji)
            }
        }
    }
}

Recap

We learned how to create a widget and we saw four concepts that make widgets so compelling on visionOS. In the next post we’ll start adding out own content.

Support our work so we can continue to bring you new examples and articles.

Download the Xcode project with this and many more examples from Step Into Vision.
Some examples are provided as standalone Xcode projects. You can find those here.

Questions or feedback?