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.

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.

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.

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.

Follow Step Into Vision