Install any skill in seconds. Free to start, no credit card required.
Get Started Free →visionOS widget patterns including mounting styles, glass/paper textures, proximity-aware layouts, and spatial widget families. Use when creating or adapting widgets for visionOS.
.claude/skills/rshankras-visionos-widgets/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 5% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 19% | 0% |
| case-13 | ✗→✓ | ▲ Improved | 25% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 68% | 0% |
| case-07 | ✗→✓ | ▲ Improved | 15% | 0% |
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.
Use this skill when the user:
.systemExtraLargePortraitWhat 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 | 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 |
This example demonstrates mounting styles, textures, families, and proximity awareness together:
swiftstruct 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.
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.
The system transitions automatically, with animation, between .default (close) and .simplified (far) based on user distance — simplify by cutting density and enlarging key info.
swiftstruct 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.
| 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 }())
In accented rendering mode, the system removes backgrounds and applies a tint color. Mark removable backgrounds so the widget renders correctly in both modes.
swiftstruct 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 ) } } }
swift#Preview("Close Up", as: .systemSmall) { MyWidget() } timelineProvider: { Provider() } #Preview("Extra Large Portrait", as: .systemExtraLargePortrait) { MyWidget() } timelineProvider: { Provider() }
| # | 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 |
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) } } }
.paper for widgets with rich imagery@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.systemExtraLargePortrait guarded with #if os(visionOS) in multiplatform targets.containerBackground(for: .widget) { } used to mark removable backgroundsshowsWidgetContainerBackground checked if foreground colors depend on backgroundOther measured skills in the registry, with their headline benchmark lift.