Agent skill

visionos-widgets

visionOS widget patterns including mounting styles, glass/paper textures, proximity-aware layouts, and spatial widget families. Use when creating or adapting widgets for visionOS.

Stars 127
Forks 10

Install this agent skill to your Project

npx add-skill https://github.com/rshankras/claude-code-apple-skills/tree/main/skills/visionos/widgets

SKILL.md

visionOS Widgets

Patterns for building widgets that live in physical space on visionOS. Covers mounting styles, textures, proximity-aware detail levels, spatial widget families, and rendering modes.

When This Skill Activates

Use this skill when the user:

  • Asks to create or adapt a widget for visionOS
  • Mentions mounting styles (elevated, recessed)
  • Wants glass or paper texture on a widget
  • Asks about proximity awareness or level of detail in widgets
  • Mentions spatial widget families or .systemExtraLargePortrait
  • Wants to control container backgrounds or rendering modes (full color vs accented)
  • Is porting an existing iOS/iPadOS widget to visionOS

Decision Tree

What do you need for your visionOS widget?
|
+- Where should the widget appear?
|  +- On a surface (table, shelf) -> .elevated (default)
|  +- Embedded in a wall -> .recessed
|  +- Both -> .supportedMountingStyles([.elevated, .recessed])
|
+- What visual treatment?
|  +- Transparent, blends with environment -> .glass (default)
|  +- Opaque, poster-like appearance -> .paper
|
+- How should it respond to user distance?
|  +- Full detail when close -> @Environment(\.levelOfDetail) == .default
|  +- Simplified when far -> @Environment(\.levelOfDetail) == .simplified
|
+- What size families?
|  +- Standard -> .systemSmall, .systemMedium, .systemLarge, .systemExtraLarge
|  +- Tall portrait -> .systemExtraLargePortrait (visionOS only)
|
+- How should colors render?
|  +- Full color (default) -> No extra work
|  +- System-tinted monochrome -> Mark backgrounds with .containerBackground(for:)

API Availability

API Minimum Version Notes
WidgetKit on visionOS visionOS 1.0 Basic widget support
.containerBackground(for: .widget) visionOS 1.0 Removable background marking
@Environment(\.showsWidgetContainerBackground) visionOS 1.0 Background visibility check
.supportedMountingStyles() visionOS 2.0 Elevated and recessed placement
.widgetTexture(.glass / .paper) visionOS 2.0 Widget surface material
@Environment(\.levelOfDetail) visionOS 2.0 Proximity-aware layouts
.systemExtraLargePortrait visionOS 2.0 Tall portrait widget family

Complete Widget Example

This example demonstrates mounting styles, textures, families, and proximity awareness together:

swift
struct MyWidget: Widget {
    var body: some WidgetConfiguration {
        StaticConfiguration(
            kind: "com.example.mywidget",
            provider: Provider()
        ) { entry in
            MyWidgetView(entry: entry)
        }
        .supportedFamilies([
            .systemSmall, .systemMedium, .systemLarge,
            .systemExtraLarge, .systemExtraLargePortrait
        ])
        .supportedMountingStyles([.elevated, .recessed])
        .widgetTexture(.glass)        // .glass is default, .paper for opaque
    }
}

Mounting styles: .elevated (default) sits on surfaces like tables. .recessed embeds into walls like a framed picture. Omit .supportedMountingStyles() to use elevated only.

Textures: .glass (default) is transparent and blends with the environment. .paper is opaque and poster-like, best for rich imagery.

Proximity Awareness (Level of Detail)

The system tracks user distance and transitions between detail levels automatically with animation.

swift
struct MyWidgetView: View {
    let entry: Provider.Entry
    @Environment(\.levelOfDetail) private var levelOfDetail

    var body: some View {
        switch levelOfDetail {
        case .default:
            VStack(alignment: .leading, spacing: 8) {
                Text(entry.title).font(.headline)
                Text(entry.subtitle).font(.subheadline).foregroundStyle(.secondary)
                DetailChart(data: entry.chartData)
            }
            .padding()
        case .simplified:
            VStack(spacing: 4) {
                Image(systemName: entry.iconName).font(.largeTitle)
                Text(entry.title).font(.headline)
            }
            .padding()
        @unknown default:
            Text(entry.title).padding()
        }
    }
}

Always handle @unknown default for forward compatibility.

Widget Families

Family Description
.systemSmall Compact square -- glanceable info
.systemMedium Wide rectangle -- two-column or list preview
.systemLarge Large square -- charts, detailed content
.systemExtraLarge Extra-large landscape -- dashboards
.systemExtraLargePortrait Extra-large portrait -- visionOS only

Guard the visionOS-only family in multiplatform targets:

swift
.supportedFamilies({
    var families: [WidgetFamily] = [.systemSmall, .systemMedium, .systemLarge]
    #if os(visionOS)
    families.append(.systemExtraLargePortrait)
    #endif
    return families
}())

Container Backgrounds and Rendering Modes

In accented rendering mode, the system removes backgrounds and applies a tint color. Mark removable backgrounds so the widget renders correctly in both modes.

swift
struct MyWidgetView: View {
    let entry: Provider.Entry
    @Environment(\.showsWidgetContainerBackground) var showsBackground

    var body: some View {
        VStack {
            Image(systemName: "star.fill").font(.largeTitle)
            Text(entry.title)
                .font(.headline)
                .foregroundStyle(showsBackground ? .white : .primary)
        }
        .padding()
        .containerBackground(for: .widget) {
            LinearGradient(
                colors: [.blue, .purple],
                startPoint: .topLeading,
                endPoint: .bottomTrailing
            )
        }
    }
}
  • Full color (default): All colors render intact.
  • Accented: Container background is removed; system applies a monochrome tint.

Previewing visionOS Widgets

swift
#Preview("Close Up", as: .systemSmall) {
    MyWidget()
} timelineProvider: {
    Provider()
}

#Preview("Extra Large Portrait", as: .systemExtraLargePortrait) {
    MyWidget()
} timelineProvider: {
    Provider()
}

Top 5 Mistakes

# Mistake Fix
1 Missing .containerBackground(for: .widget) -- accented mode renders blank Always wrap backgrounds in .containerBackground(for: .widget) { }
2 Ignoring levelOfDetail -- detailed views unreadable from across the room Provide a .simplified layout with larger text, fewer elements
3 Using .systemExtraLargePortrait on iOS -- build error or runtime crash Guard with #if os(visionOS) or visionOS-only targets
4 Hardcoding colors that clash with glass texture Use .foregroundStyle(.primary / .secondary) and system colors
5 No @unknown default in levelOfDetail switch Always include for forward compatibility

Anti-Patterns

swift
// ❌ No container background — accented mode shows nothing
struct BadWidgetView: View {
    var body: some View {
        ZStack {
            Color.blue  // Not marked as removable
            Text("Hello")
        }
    }
}

// ✅ Background marked as removable
struct GoodWidgetView: View {
    var body: some View {
        Text("Hello")
            .containerBackground(for: .widget) { Color.blue }
    }
}
swift
// ❌ Same complex layout at all distances
struct BadProximityView: View {
    var body: some View {
        VStack {
            Text(entry.title).font(.caption2)  // Unreadable far away
            DetailChart(data: entry.data)
        }
    }
}

// ✅ Simplified layout when far away
struct GoodProximityView: View {
    @Environment(\.levelOfDetail) private var levelOfDetail
    var body: some View {
        switch levelOfDetail {
        case .default: DetailedLayout(entry: entry)
        case .simplified: SimplifiedLayout(entry: entry)
        @unknown default: SimplifiedLayout(entry: entry)
        }
    }
}

Review Checklist

Mounting and Texture

  • Mounting style explicitly set if widget should appear recessed or support both
  • Texture set to .paper for widgets with rich imagery
  • Widget tested in both elevated and recessed placements (if both supported)

Proximity Awareness

  • @Environment(\.levelOfDetail) provides simplified layout for distant viewers
  • .simplified layout uses larger text, fewer elements, high-contrast visuals
  • @unknown default case present in levelOfDetail switch

Families and Layout

  • .systemExtraLargePortrait guarded with #if os(visionOS) in multiplatform targets
  • Widget content adapts to each supported family size
  • Layout tested in all declared family sizes via Xcode previews

Backgrounds and Rendering

  • .containerBackground(for: .widget) { } used to mark removable backgrounds
  • Widget renders correctly in both full color and accented modes
  • showsWidgetContainerBackground checked if foreground colors depend on background
  • System semantic colors used for glass texture compatibility

References

Didn't find tool you were looking for?

Be as detailed as possible for better results