Buttons become unresponsive after using .windowStyle(.plain) with auto-hiding menu

I'm developing a visionOS panorama viewer app where I need to implement an auto-hiding floating menu in immersive space. The menu should: Show for 3 seconds when entering immersive mode Auto-hide after 3 seconds, Reappear when user taps anywhere (using SpatialTapGesture). Buttons should respond to gaze + pinch interaction

The Problem:

When I add .windowStyle(.plain) to achieve transparent window background for the auto-hide effect, all buttons in the menu become completely unresponsive to gaze + pinch interaction. The buttons only respond to direct finger touch (poking).

Without .windowStyle(.plain): Buttons work correctly with gaze + pinch, but I cannot achieve transparent window background for hiding. With .windowStyle(.plain): Window can be transparent, but buttons lose gaze + pinch interaction.

Code:

App.swift:

@main
struct MyApp: App {
    @StateObject private var model = AppModel()

    var body: some Scene {
        WindowGroup(id: "MainWindow") {
            ContentView()
                .environmentObject(model)
        }
        .defaultSize(width: 900, height: 700)
        .windowResizability(.contentSize)
        .windowStyle(.plain)  // <-- This causes the interaction issue

        ImmersiveSpace(id: "ImmersiveSpace") {
            ImmersiveView()
                .environmentObject(model)
        }
    }
}

ContentView.swift (simplified):

struct ContentView: View {
    @EnvironmentObject var model: AppModel
    @State private var isMenuVisible: Bool = true
    
    var body: some View {
        VStack {
            if model.isImmersiveViewActive {
                if isMenuVisible {
                    // This menu's buttons don't respond to gaze+pinch
                    immersiveControlMenu
                }
            } else {
                mainMenuButtons
            }
        }
        .glassBackgroundEffect()
    }
    
    private var immersiveControlMenu: some View {
        HStack {
            Button("Exit") {
                exitImmersiveSpace()
            }
            .buttonStyle(.bordered)  // Also tried .plain, same issue
        }
        .padding()
        .glassBackgroundEffect()
    }
}

ImmersiveView.swift:

struct ImmersiveView: View {
    @EnvironmentObject var model: AppModel
    
    var body: some View {
        RealityView { content in
            // Panorama sphere
            let sphere = ModelEntity(mesh: .generateSphere(radius: 1000), materials: [material])
            content.add(sphere)
            
            // Tap detector for menu toggle
            let tapDetector = Entity()
            tapDetector.components.set(CollisionComponent(shapes: [.generateSphere(radius: 900)]))
            tapDetector.components.set(InputTargetComponent())
            content.add(tapDetector)
        }
        .gesture(
            SpatialTapGesture()
                .targetedToAnyEntity()
                .onEnded { _ in
                    model.shouldShowMenu = true
                }
        )
    }
}

Environment: Xcode 26.2 visionOS 26.3 Vision Pro device

Questions:

  1. Is .windowStyle(.plain) expected to affect button interaction behavior?
  2. What is the recommended approach to achieve a transparent/hidden window in immersive mode while maintaining button interactivity?
  3. Is there an alternative to .windowStyle(.plain) for hiding window chrome in visionOS?

Thank you for any guidance!

Answered by Vision Pro Engineer in 874599022

Hey @Travel_Immersive,

No, .windowStyle(.plain) should not affect button interaction behavior.

In terms of creating a window that completely hides itself, I might suggest that you try a different approach. While you can have a window with no content and set the preferred visibility of the system controls to hidden, this is not the best approach as it still could leave the user with floating system UI that isn't attached to anything. Additionally, as this is a window that has system UI, the user could close this window at any time and you'd want to account for this.

It sounds like your experience might be better suited to using an ViewAttachmentComponent. Have you considered placing your immersiveControlMenu as the root view of a ViewAttachmentComponent in your immersive view? This way you can animate the visibility of this view and you fully control when and where this view is presented.

Let me know if you what you think,
Michael

Can you try commenting out your entity/SpatialTapGesture code? I have a hunch that the entity may be intercepting taps intended for the button. visionOS has some odd issues when we place gestures on entities that the user will be inside of.

You may want to take a look at SpatialEventGesture instead of using the tap in an entity. This will let you listen to input anywhere in the immersive space without using a specific entity.

Docs: https://developer.apple.com/documentation/swiftui/spatialeventgesture

Example Code: https://stepinto.vision/example-code/spatial-event-gesture/


Is .windowStyle(.plain) expected to affect button interaction behavior?

No, I've used buttons in many plain windows. Unless visionOS 26.3 introduced a new bug, it seems like something else is going on here.

What is the recommended approach to achieve a transparent/hidden window in immersive mode while maintaining button interactivity?

You're on the right track, see above about the entity gesture intercepting input.

Is there an alternative to .windowStyle(.plain) for hiding window chrome in visionOS?

No, not for hiding the glass background of the a window. .plain window style is the only way.

Hey @Travel_Immersive,

No, .windowStyle(.plain) should not affect button interaction behavior.

In terms of creating a window that completely hides itself, I might suggest that you try a different approach. While you can have a window with no content and set the preferred visibility of the system controls to hidden, this is not the best approach as it still could leave the user with floating system UI that isn't attached to anything. Additionally, as this is a window that has system UI, the user could close this window at any time and you'd want to account for this.

It sounds like your experience might be better suited to using an ViewAttachmentComponent. Have you considered placing your immersiveControlMenu as the root view of a ViewAttachmentComponent in your immersive view? This way you can animate the visibility of this view and you fully control when and where this view is presented.

Let me know if you what you think,
Michael

Hi Michael,

Thank you for your previous suggestion to use ViewAttachmentComponent for immersive controls. I've implemented it, However, I'm still facing two critical issues:

Issue 1: Buttons in ViewAttachmentComponent Not Responding to Gaze + Pinch

The buttons inside my ViewAttachmentComponent are visible and render correctly, but they do not respond to eye gaze + pinch gestures. This defeats the purpose of using visionOS's hands-free interaction model.

My Implementation:

ImmersiveView.swift (for panoramic images):

struct ImmersiveView: View {
    @EnvironmentObject var model: AppModel
    @State private var menuEntity: Entity?
    @State private var isMenuVisible: Bool = true
    
    var body: some View {
        RealityView { content in
            // Invisible sphere for gesture detection
            let tapDetector = Entity()
            tapDetector.name = "TapDetector"
            tapDetector.components.set(CollisionComponent(shapes: [.generateSphere(radius: 900)]))
            tapDetector.components.set(InputTargetComponent())
            content.add(tapDetector)
            
            // Create control menu using ViewAttachmentComponent
            let menuView = ImageControlMenuView(onExit: exitImmersiveMode)
            
            let attachmentEntity = Entity()
            attachmentEntity.name = "ImageControlMenu"
            attachmentEntity.components.set(ViewAttachmentComponent(rootView: menuView))
            attachmentEntity.position = [0, 1.3, -1.5]
            attachmentEntity.components.set(BillboardComponent())
            
            menuEntity = attachmentEntity
            content.add(attachmentEntity)
        } update: { content in
            if let menu = menuEntity {
                menu.isEnabled = isMenuVisible
            }
        }
        .gesture(
            SpatialTapGesture()
                .targetedToAnyEntity()
                .onEnded { _ in
                    // This gesture works - menu shows/hides correctly
                    showMenuAndScheduleHide()
                }
        )
    }
}

// Control Menu View
struct ImageControlMenuView: View {
    let onExit: () -> Void
    
    var body: some View {
        HStack(spacing: 16) {
            Button(action: {
                print("Exit button tapped")  // Never prints with gaze+pinch
                onExit()
            }) {
                HStack(spacing: 8) {
                    Image(systemName: "xmark.circle.fill")
                        .font(.title2)
                    Text("Exit")
                        .font(.headline)
                }
                .padding(.horizontal, 20)
                .padding(.vertical, 12)
            }
            .buttonStyle(.bordered)
            .tint(.white)
        }
        .padding(16)
        .glassBackgroundEffect()
    }
}

**Question: **

Is there additional configuration required for ViewAttachmentComponent to enable gaze + pinch interaction? Do I need to add InputTargetComponent or CollisionComponent to the attachment entity itself?

Issue 2: Positioning Menu at User's Gaze Direction

Currently, my menu appears at a fixed position in 3D space ([0, 1.3, -1.5]). I want to implement behavior similar to Apple TV's immersive video controls, where the menu appears wherever the user is looking when they perform a pinch gesture.

I'm using BillboardComponent which makes the menu face the user, but the position remains fixed.

Desired behavior: User pinches anywhere in space Menu appears directly in front of user's current gaze direction

Menu auto-hides after 3 seconds

**Question: ** What's the recommended approach to: Get the user's current head position and gaze direction in an ImmersiveSpace?

Position the ViewAttachmentComponent entity relative to the user's gaze when the pinch gesture is detected?

Should I use ARKit's head tracking (ARSession with WorldTrackingProvider), or is there a simpler visionOS API for this use case?

Environment

visionOS: 26.3 Xcode: 26.2 Deployment Target: visionOS 26.0 Device: Apple Vision Pro

Summary

Gaze + Pinch not working: Buttons in ViewAttachmentComponent don't respond to eye&hand interaction

Fixed position: Need guidance on positioning attachment at user's gaze direction dynamically

Any further guidance would be greatly appreciated!

Hey @Travel_Immersive,

As @JosephSimpson outlined earlier, I believe the interactions are being consumed by the InputTargetComponent on the tapDetector. You might consider removing the InputTargetComponent while the ViewAttachmentComponent is presented. I could take a closer look at your project if you would open a bug report, include a working project that replicates the issue, and post the FB number here once you do.

Take a look at Canyon Crosser from last year's WWDC for a throughout example of using ViewAttachmentComponent. This sample doesn't include a large tapDetector but does show the expected behavior for this component.

As outlined in Design considerations for vision and motion, whenever possible, avoid using head-locked content. If you want to position an entity relative to a user's gaze you would need an ARKitSession. See Placing entities using head and device transform for more details on this approach.

Thanks,
Michael

Buttons become unresponsive after using .windowStyle(.plain) with auto-hiding menu
 
 
Q