---
name: rshankras/visionos-widgets
source: https://app.decimal.ai/s/rshankras-visionos-widgets@1/SKILL.md
source_sha256: ee76bcce2622
---

# 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. Recessed works only on vertical surfaces — placements on horizontal surfaces are always elevated. On horizontal surfaces the system also applies a gentle tilt toward the user; design for that angle rather than fighting it.

**Textures**: `.glass` (default) blends with the environment; `.paper` is opaque, best for rich imagery.

## Spatial Behavior (WWDC25)

Widgets are permanent, physical-surface-only room fixtures — they persist across sessions, room changes, and power cycles, and multiple instances can coexist in a room.

- **User resizing**: a corner affordance lets users scale a widget from **75% to 125%** of its template size — layouts must survive the entire range.
- **Frames**: users pick from **five frame widths** (thin to thick), independent of template size. The recessed style fixes the frame width.
- **Frame tinting**: the widget frame always receives the user's color tint and cannot opt out. Even when the background opts out of tinting, foregrounds must hold up under all **7 light and 7 dark** system palettes.
- **Assets**: widgets render at real-world scale — ship high-resolution assets so imagery stays sharp at close range.

## Proximity Awareness (Level of Detail)

The system transitions automatically, with animation, between `.default` (close) and `.simplified` (far) based on user distance — simplify by cutting density and enlarging key info.

```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; wall-art "statement" widgets |

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)
- [ ] Layout survives user resizing from 75% to 125% of the template size

### 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
- [ ] Foreground legible under all 7 light and 7 dark frame-tint palettes

## References

- [Widgets for visionOS (WWDC)](https://developer.apple.com/documentation/widgetkit/widgets-for-visionos)
- [WidgetKit](https://developer.apple.com/documentation/widgetkit)
- [visionOS Human Interface Guidelines - Widgets](https://developer.apple.com/design/human-interface-guidelines/widgets)