[Windows] Fix Narrator announcing ContentView children twice when Description is set#33979
Conversation
There was a problem hiding this comment.
Pull request overview
Fixes a Windows Narrator accessibility issue where ContentView content was announced twice when SemanticProperties.Description is set, by customizing the UI Automation peer used by the Windows ContentPanel.
Changes:
- Added a custom
AutomationPeerforContentPanelto alter control type/localized type and hide children whenAutomationProperties.Name(Description) is set. - Updated Windows PublicAPI unshipped list to include the new
OnCreateAutomationPeer()override. - Added Windows device tests validating automation peer behavior with/without Description.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| src/Core/src/Platform/Windows/ContentPanel.cs | Introduces a custom FrameworkElementAutomationPeer to prevent duplicate Narrator announcements when a Description/Name is present. |
| src/Core/src/PublicAPI/net-windows/PublicAPI.Unshipped.txt | Records the new protected override as an unshipped Windows API surface change. |
| src/Controls/tests/DeviceTests/Elements/ContentView/ContentViewTests.Windows.cs | Adds device tests validating control type, localized control type, and child visibility behavior under UIA. |
🤖 AI Summary📊 Expand Full Review —
|
| # | Source | Approach | Test Result | Files Changed | Notes |
|---|---|---|---|---|---|
| PR | PR #33979 | Custom ContentPanelAutomationPeer nested class; hides children and uses AutomationControlType.Text when AutomationProperties.Name is set |
❌ FAILED (Gate — build env issue) | ContentPanel.cs, ContentViewTests.Windows.cs, PublicAPI.Unshipped.txt |
Gate failure is environment constraint, not code defect |
🔧 Fix — Analysis & Comparison
Fix Candidates
| # | Source | Approach | Test Result | Files Changed | Notes |
|---|---|---|---|---|---|
| 1 | try-fix (claude-opus-4.6) | Set AccessibilityView.Raw on ContentPanel children in UpdateSemantics (ViewExtensions.cs) when description is set |
ViewExtensions.cs, ContentViewTests.Windows.cs |
Build env: WindowsAppSDKSelfContained architecture error | |
| 2 | try-fix (claude-sonnet-4.6) | Cached _hasSemanticDescription flag via RegisterPropertyChangedCallback(NameProperty); peer reads field instead of calling GetName() |
ContentPanel.cs, PublicAPI.Unshipped.txt |
Build env: same architecture error | |
| 3 | try-fix (gpt-5.3-codex) | MauiPanelAutomationPeer override in MauiPanel base class instead of ContentPanel |
MauiPanel.cs, ContentViewTests.Windows.cs |
Build env: same architecture error | |
| 4 | try-fix (gpt-5.4) | Handler-level fix: ContentViewHandler.Windows.cs sets AccessibilityView.Raw on child element when parent has description |
ContentViewHandler.Windows.cs, ContentViewTests.Windows.cs |
Build env: same architecture error | |
| PR | PR #33979 | Custom ContentPanelAutomationPeer nested class; hides children and uses AutomationControlType.Text when name is set |
❌ FAILED (Gate — build env) | ContentPanel.cs, ContentViewTests.Windows.cs, PublicAPI.Unshipped.txt |
Gate failure is build env issue, not code defect |
Cross-Pollination
| Model | Round | New Ideas? | Details |
|---|---|---|---|
| claude-opus-4.6 | 2 | NO NEW IDEAS | All 5 approaches cover the two realistic solution categories (custom peer vs. AccessibilityView.Raw). No new variants remain. PR's peer-based approach is the standard WinUI pattern. |
Exhausted: Yes — all 4 models queried; cross-pollination complete; no new ideas.
Selected Fix: PR's fix — ContentPanelAutomationPeer approach
Reason: All try-fix alternatives were BLOCKED by the same environment issue. Code analysis confirms the PR's approach is architecturally sound and follows the standard WinUI pattern for this exact problem. No empirically superior alternative could be identified.
📋 Report — Final Recommendation
⚠️ Final Recommendation: REQUEST CHANGES
Phase Status
| Phase | Status | Notes |
|---|---|---|
| Pre-Flight | ✅ COMPLETE | Windows-only a11y fix; 2 implementation files, 1 test file, 1 public API entry |
| Gate | ❌ FAILED | Windows — both "without fix" and "with fix" failed with WindowsAppSDKSelfContained requires a supported Windows architecture (build env) |
| Try-Fix | ✅ COMPLETE | 4 attempts, 0 passing (all BLOCKED by same build env issue); cross-pollination exhausted |
| Report | ✅ COMPLETE |
Summary
PR #33979 addresses a real Windows accessibility bug (#33373) where ContentView with SemanticProperties.Description causes Narrator to announce content twice. The fix is architecturally sound and the prior inline review comments have been addressed. However, the Gate ❌ FAILED — both "without fix" and "with fix" builds failed with WindowsAppSDKSelfContained requires a supported Windows architecture in Graphics.csproj. All 4 try-fix attempts encountered the same environment blocker, and cross-pollination found no new ideas.
The Gate failure is a build environment constraint, not a code logic defect. The CI agent running this review lacks a supported Windows architecture for WinAppSDK self-contained deployment. CI pipelines (maui-pr-devicetests) were triggered by a team member on 2026-04-09 and should validate the fix on a proper Windows build agent.
Root Cause (of the Bug)
WinUI TextBlock (used by Label) automatically exposes its Text to UIA. ContentPanel's default FrameworkElementAutomationPeer exposes both AutomationProperties.Name (set via SemanticProperties.Description) and all child elements. Windows has no single built-in API like Android's NoHideDescendants to suppress children while keeping the parent accessible, so both Tab navigation and Browse mode announce duplicate content.
Fix Quality
Implementation (ContentPanel.cs): ✅ Sound
GetChildrenCore()→nullwhen description is set (prevents duplicate UIA tree traversal)GetAutomationControlTypeCore()→Text(enables Browse mode navigation without "group"/"custom" suffixes)GetLocalizedControlTypeCore()→""(suppresses "text" announcement suffix)HasDescriptioncleanly centralizes the non-empty name check- Follows the existing
MauiButtonAutomationPeerpattern in MAUI - Prior inline review comments (indentation, comment accuracy) are resolved
Tests (ContentViewTests.Windows.cs): ✅ Correct
GetOrCreateAutomationPeeruses directnew ContentPanel.ContentPanelAutomationPeer(contentPanel)instantiation, correctly bypassing WinUI3CreatePeerForElement()limitationAssert.Null(children)is correct for WinUI3 (nullGetChildrenCore()→ nullGetChildren())Assert.NotEmpty(children)for the no-description case validates children remain visible[Category(TestCategory.ContentView)]inherited from class-level attribute inContentViewTests.cs- Prior inline review comment ("assert NotEmpty") is resolved
Public API (PublicAPI.Unshipped.txt): ✅ Correct
override Microsoft.Maui.Platform.ContentPanel.OnCreateAutomationPeer()properly declared
Minor issue (cosmetic):
ContentPanelAutomationPeeris declared asinternal partial class— thepartialmodifier is unnecessary since the class is not split across files. Should beinternal class ContentPanelAutomationPeer.
Selected Fix: PR's fix
All try-fix alternatives were BLOCKED by the same environment issue. Code analysis confirms the PR's ContentPanelAutomationPeer approach is the best available solution for the WinUI platform constraints.
Required Action
The PR author should confirm the tests pass on a proper Windows x64 CI environment (maui-pr-devicetests was triggered 2026-04-09). If CI passes, the only remaining change is cosmetic:
- Remove
partialfromContentPanelAutomationPeerdeclaration:- internal partial class ContentPanelAutomationPeer : FrameworkElementAutomationPeer + internal class ContentPanelAutomationPeer : FrameworkElementAutomationPeer
The test has now been updated and verified. It is working as expected. |
Backport of dotnet#34801 to main. The official pack pipeline doesn't need iOS simulators — it only builds and packs NuGet packages. The simulator install step is timing out on macOS agents, blocking BAR build production. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
/azp run maui-pr-uitests , maui-pr-devicetests |
|
Azure Pipelines successfully started running 2 pipeline(s). |
Code Review — PR #33979Independent AssessmentWhat this changes: Adds a custom Inferred motivation: When a Reconciliation with PR NarrativeAgreement: My independent assessment matches the PR description. The root cause analysis is correct — WinUI has no equivalent to Android's Findings
|
…cription is set (#33979) <!-- Please let the below note in for people that find this PR --> > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! ### Root Cause WinUI `TextBlock` (used by `Label`) automatically exposes its `Text` to UI Automation. `ContentPanel` uses the default `FrameworkElementAutomationPeer`, which exposes both the parent’s `AutomationProperties.Name` and all child elements. Unlike Android (`NoHideDescendants`) or iOS (`AccessibilityElementsHidden`), Windows has no single property to hide descendants while keeping the parent accessible. As a result, both Tab navigation and Browse mode announced duplicate content. ### Description of Change Implemented a custom `ContentPanelAutomationPeer` that overrides three core UI Automation methods (GetAutomationControlTypeCore , `GetLocalizedControlTypeCore`, `GetChildrenCore`) to conditionally modify behavior when Description is present. When a Description exists, the control is exposed as `AutomationControlType.Text` (enables browse mode navigation; alternatives like Custom announce "custom" suffix, Group causes browse mode to skip the element), the "text" announcement suffix is suppressed via empty `GetLocalizedControlTypeCore()` return, and child elements are hidden by returning null from `GetChildrenCore()` to prevent duplication. When no Description is present, default behavior is preserved with `AutomationControlType.Custom` and children remain accessible. The `HasDescription` helper property centralizes the non-empty Description check across all three override methods, ensuring consistent conditional logic following the MAUI `AutomationPeer patterns. ### Issues Fixed Fixes #33373 ### Platforms Tested - [ ] iOS - [x] Android - [x] Windows - [x] Mac ### Screenshots | Before Fix | After Fix | |------------|-----------| | <video width="350" alt="withoutfix" src="https://github.com/user-attachments/assets/c5f8f114-fbeb-42d1-8601-e75dad57a1a7" /> | <video width="350" alt="withfix" src="https://github.com/user-attachments/assets/d2405df5-64f3-4cd9-8b38-40911ce4fbd6" /> | ---------
…cription is set (dotnet#33979) <!-- Please let the below note in for people that find this PR --> > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! ### Root Cause WinUI `TextBlock` (used by `Label`) automatically exposes its `Text` to UI Automation. `ContentPanel` uses the default `FrameworkElementAutomationPeer`, which exposes both the parent’s `AutomationProperties.Name` and all child elements. Unlike Android (`NoHideDescendants`) or iOS (`AccessibilityElementsHidden`), Windows has no single property to hide descendants while keeping the parent accessible. As a result, both Tab navigation and Browse mode announced duplicate content. ### Description of Change Implemented a custom `ContentPanelAutomationPeer` that overrides three core UI Automation methods (GetAutomationControlTypeCore , `GetLocalizedControlTypeCore`, `GetChildrenCore`) to conditionally modify behavior when Description is present. When a Description exists, the control is exposed as `AutomationControlType.Text` (enables browse mode navigation; alternatives like Custom announce "custom" suffix, Group causes browse mode to skip the element), the "text" announcement suffix is suppressed via empty `GetLocalizedControlTypeCore()` return, and child elements are hidden by returning null from `GetChildrenCore()` to prevent duplication. When no Description is present, default behavior is preserved with `AutomationControlType.Custom` and children remain accessible. The `HasDescription` helper property centralizes the non-empty Description check across all three override methods, ensuring consistent conditional logic following the MAUI `AutomationPeer patterns. ### Issues Fixed Fixes dotnet#33373 ### Platforms Tested - [ ] iOS - [x] Android - [x] Windows - [x] Mac ### Screenshots | Before Fix | After Fix | |------------|-----------| | <video width="350" alt="withoutfix" src="https://github.com/user-attachments/assets/c5f8f114-fbeb-42d1-8601-e75dad57a1a7" /> | <video width="350" alt="withfix" src="https://github.com/user-attachments/assets/d2405df5-64f3-4cd9-8b38-40911ce4fbd6" /> | ---------
…cription is set (#33979) <!-- Please let the below note in for people that find this PR --> > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! ### Root Cause WinUI `TextBlock` (used by `Label`) automatically exposes its `Text` to UI Automation. `ContentPanel` uses the default `FrameworkElementAutomationPeer`, which exposes both the parent’s `AutomationProperties.Name` and all child elements. Unlike Android (`NoHideDescendants`) or iOS (`AccessibilityElementsHidden`), Windows has no single property to hide descendants while keeping the parent accessible. As a result, both Tab navigation and Browse mode announced duplicate content. ### Description of Change Implemented a custom `ContentPanelAutomationPeer` that overrides three core UI Automation methods (GetAutomationControlTypeCore , `GetLocalizedControlTypeCore`, `GetChildrenCore`) to conditionally modify behavior when Description is present. When a Description exists, the control is exposed as `AutomationControlType.Text` (enables browse mode navigation; alternatives like Custom announce "custom" suffix, Group causes browse mode to skip the element), the "text" announcement suffix is suppressed via empty `GetLocalizedControlTypeCore()` return, and child elements are hidden by returning null from `GetChildrenCore()` to prevent duplication. When no Description is present, default behavior is preserved with `AutomationControlType.Custom` and children remain accessible. The `HasDescription` helper property centralizes the non-empty Description check across all three override methods, ensuring consistent conditional logic following the MAUI `AutomationPeer patterns. ### Issues Fixed Fixes #33373 ### Platforms Tested - [ ] iOS - [x] Android - [x] Windows - [x] Mac ### Screenshots | Before Fix | After Fix | |------------|-----------| | <video width="350" alt="withoutfix" src="https://github.com/user-attachments/assets/c5f8f114-fbeb-42d1-8601-e75dad57a1a7" /> | <video width="350" alt="withfix" src="https://github.com/user-attachments/assets/d2405df5-64f3-4cd9-8b38-40911ce4fbd6" /> | ---------
…cription is set (#33979) <!-- Please let the below note in for people that find this PR --> > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! ### Root Cause WinUI `TextBlock` (used by `Label`) automatically exposes its `Text` to UI Automation. `ContentPanel` uses the default `FrameworkElementAutomationPeer`, which exposes both the parent’s `AutomationProperties.Name` and all child elements. Unlike Android (`NoHideDescendants`) or iOS (`AccessibilityElementsHidden`), Windows has no single property to hide descendants while keeping the parent accessible. As a result, both Tab navigation and Browse mode announced duplicate content. ### Description of Change Implemented a custom `ContentPanelAutomationPeer` that overrides three core UI Automation methods (GetAutomationControlTypeCore , `GetLocalizedControlTypeCore`, `GetChildrenCore`) to conditionally modify behavior when Description is present. When a Description exists, the control is exposed as `AutomationControlType.Text` (enables browse mode navigation; alternatives like Custom announce "custom" suffix, Group causes browse mode to skip the element), the "text" announcement suffix is suppressed via empty `GetLocalizedControlTypeCore()` return, and child elements are hidden by returning null from `GetChildrenCore()` to prevent duplication. When no Description is present, default behavior is preserved with `AutomationControlType.Custom` and children remain accessible. The `HasDescription` helper property centralizes the non-empty Description check across all three override methods, ensuring consistent conditional logic following the MAUI `AutomationPeer patterns. ### Issues Fixed Fixes #33373 ### Platforms Tested - [ ] iOS - [x] Android - [x] Windows - [x] Mac ### Screenshots | Before Fix | After Fix | |------------|-----------| | <video width="350" alt="withoutfix" src="https://github.com/user-attachments/assets/c5f8f114-fbeb-42d1-8601-e75dad57a1a7" /> | <video width="350" alt="withfix" src="https://github.com/user-attachments/assets/d2405df5-64f3-4cd9-8b38-40911ce4fbd6" /> | ---------
…cription is set (#33979) <!-- Please let the below note in for people that find this PR --> > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! ### Root Cause WinUI `TextBlock` (used by `Label`) automatically exposes its `Text` to UI Automation. `ContentPanel` uses the default `FrameworkElementAutomationPeer`, which exposes both the parent’s `AutomationProperties.Name` and all child elements. Unlike Android (`NoHideDescendants`) or iOS (`AccessibilityElementsHidden`), Windows has no single property to hide descendants while keeping the parent accessible. As a result, both Tab navigation and Browse mode announced duplicate content. ### Description of Change Implemented a custom `ContentPanelAutomationPeer` that overrides three core UI Automation methods (GetAutomationControlTypeCore , `GetLocalizedControlTypeCore`, `GetChildrenCore`) to conditionally modify behavior when Description is present. When a Description exists, the control is exposed as `AutomationControlType.Text` (enables browse mode navigation; alternatives like Custom announce "custom" suffix, Group causes browse mode to skip the element), the "text" announcement suffix is suppressed via empty `GetLocalizedControlTypeCore()` return, and child elements are hidden by returning null from `GetChildrenCore()` to prevent duplication. When no Description is present, default behavior is preserved with `AutomationControlType.Custom` and children remain accessible. The `HasDescription` helper property centralizes the non-empty Description check across all three override methods, ensuring consistent conditional logic following the MAUI `AutomationPeer patterns. ### Issues Fixed Fixes #33373 ### Platforms Tested - [ ] iOS - [x] Android - [x] Windows - [x] Mac ### Screenshots | Before Fix | After Fix | |------------|-----------| | <video width="350" alt="withoutfix" src="https://github.com/user-attachments/assets/c5f8f114-fbeb-42d1-8601-e75dad57a1a7" /> | <video width="350" alt="withfix" src="https://github.com/user-attachments/assets/d2405df5-64f3-4cd9-8b38-40911ce4fbd6" /> | ---------
…cription is set (#33979) <!-- Please let the below note in for people that find this PR --> > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! ### Root Cause WinUI `TextBlock` (used by `Label`) automatically exposes its `Text` to UI Automation. `ContentPanel` uses the default `FrameworkElementAutomationPeer`, which exposes both the parent’s `AutomationProperties.Name` and all child elements. Unlike Android (`NoHideDescendants`) or iOS (`AccessibilityElementsHidden`), Windows has no single property to hide descendants while keeping the parent accessible. As a result, both Tab navigation and Browse mode announced duplicate content. ### Description of Change Implemented a custom `ContentPanelAutomationPeer` that overrides three core UI Automation methods (GetAutomationControlTypeCore , `GetLocalizedControlTypeCore`, `GetChildrenCore`) to conditionally modify behavior when Description is present. When a Description exists, the control is exposed as `AutomationControlType.Text` (enables browse mode navigation; alternatives like Custom announce "custom" suffix, Group causes browse mode to skip the element), the "text" announcement suffix is suppressed via empty `GetLocalizedControlTypeCore()` return, and child elements are hidden by returning null from `GetChildrenCore()` to prevent duplication. When no Description is present, default behavior is preserved with `AutomationControlType.Custom` and children remain accessible. The `HasDescription` helper property centralizes the non-empty Description check across all three override methods, ensuring consistent conditional logic following the MAUI `AutomationPeer patterns. ### Issues Fixed Fixes #33373 ### Platforms Tested - [ ] iOS - [x] Android - [x] Windows - [x] Mac ### Screenshots | Before Fix | After Fix | |------------|-----------| | <video width="350" alt="withoutfix" src="https://github.com/user-attachments/assets/c5f8f114-fbeb-42d1-8601-e75dad57a1a7" /> | <video width="350" alt="withfix" src="https://github.com/user-attachments/assets/d2405df5-64f3-4cd9-8b38-40911ce4fbd6" /> | ---------
…cription is set (#33979) <!-- Please let the below note in for people that find this PR --> > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! ### Root Cause WinUI `TextBlock` (used by `Label`) automatically exposes its `Text` to UI Automation. `ContentPanel` uses the default `FrameworkElementAutomationPeer`, which exposes both the parent’s `AutomationProperties.Name` and all child elements. Unlike Android (`NoHideDescendants`) or iOS (`AccessibilityElementsHidden`), Windows has no single property to hide descendants while keeping the parent accessible. As a result, both Tab navigation and Browse mode announced duplicate content. ### Description of Change Implemented a custom `ContentPanelAutomationPeer` that overrides three core UI Automation methods (GetAutomationControlTypeCore , `GetLocalizedControlTypeCore`, `GetChildrenCore`) to conditionally modify behavior when Description is present. When a Description exists, the control is exposed as `AutomationControlType.Text` (enables browse mode navigation; alternatives like Custom announce "custom" suffix, Group causes browse mode to skip the element), the "text" announcement suffix is suppressed via empty `GetLocalizedControlTypeCore()` return, and child elements are hidden by returning null from `GetChildrenCore()` to prevent duplication. When no Description is present, default behavior is preserved with `AutomationControlType.Custom` and children remain accessible. The `HasDescription` helper property centralizes the non-empty Description check across all three override methods, ensuring consistent conditional logic following the MAUI `AutomationPeer patterns. ### Issues Fixed Fixes #33373 ### Platforms Tested - [ ] iOS - [x] Android - [x] Windows - [x] Mac ### Screenshots | Before Fix | After Fix | |------------|-----------| | <video width="350" alt="withoutfix" src="https://github.com/user-attachments/assets/c5f8f114-fbeb-42d1-8601-e75dad57a1a7" /> | <video width="350" alt="withfix" src="https://github.com/user-attachments/assets/d2405df5-64f3-4cd9-8b38-40911ce4fbd6" /> | ---------
…cription is set (dotnet#33979) <!-- Please let the below note in for people that find this PR --> > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! ### Root Cause WinUI `TextBlock` (used by `Label`) automatically exposes its `Text` to UI Automation. `ContentPanel` uses the default `FrameworkElementAutomationPeer`, which exposes both the parent’s `AutomationProperties.Name` and all child elements. Unlike Android (`NoHideDescendants`) or iOS (`AccessibilityElementsHidden`), Windows has no single property to hide descendants while keeping the parent accessible. As a result, both Tab navigation and Browse mode announced duplicate content. ### Description of Change Implemented a custom `ContentPanelAutomationPeer` that overrides three core UI Automation methods (GetAutomationControlTypeCore , `GetLocalizedControlTypeCore`, `GetChildrenCore`) to conditionally modify behavior when Description is present. When a Description exists, the control is exposed as `AutomationControlType.Text` (enables browse mode navigation; alternatives like Custom announce "custom" suffix, Group causes browse mode to skip the element), the "text" announcement suffix is suppressed via empty `GetLocalizedControlTypeCore()` return, and child elements are hidden by returning null from `GetChildrenCore()` to prevent duplication. When no Description is present, default behavior is preserved with `AutomationControlType.Custom` and children remain accessible. The `HasDescription` helper property centralizes the non-empty Description check across all three override methods, ensuring consistent conditional logic following the MAUI `AutomationPeer patterns. ### Issues Fixed Fixes dotnet#33373 ### Platforms Tested - [ ] iOS - [x] Android - [x] Windows - [x] Mac ### Screenshots | Before Fix | After Fix | |------------|-----------| | <video width="350" alt="withoutfix" src="https://github.com/user-attachments/assets/c5f8f114-fbeb-42d1-8601-e75dad57a1a7" /> | <video width="350" alt="withfix" src="https://github.com/user-attachments/assets/d2405df5-64f3-4cd9-8b38-40911ce4fbd6" /> | ---------
## What's Coming .NET MAUI inflight/candidate introduces significant improvements across all platforms with focus on quality, performance, and developer experience. This release includes 85 commits with various improvements, bug fixes, and enhancements. ## Button - [Android, iOS] Button: Fix VisualState properties not restored when leaving custom state by @BagavathiPerumal in #33346 <details> <summary>🔧 Fixes</summary> - [Button VisualStates do not work](#19690) </details> ## CollectionView - Fix CollectionView grid spacing updates for first row and column by @KarthikRajaKalaimani in #34527 <details> <summary>🔧 Fixes</summary> - [[MAUI] I2_Vertical grid for horizontal Item Spacing and Vertical Item Spacing - horizontally updating the spacing only applies to the second column](#34257) </details> - CarouselView: Fix cascading PositionChanged/CurrentItemChanged events on collection update by @praveenkumarkarunanithi in #31275 <details> <summary>🔧 Fixes</summary> - [[Windows] CurrentItemChangedEventArgs and PositionChangedEventArgs Not Working Properly in CarouselView](#29529) </details> - [Windows] Fixed ItemSpacing doesn't work in Carousel View by @SubhikshaSf4851 in #30014 <details> <summary>🔧 Fixes</summary> - [ItemSpacing on CarouselView is not applied on Windows.](#29772) </details> - Fix CollectionView not scrolling to top on iOS status bar tap by @jfversluis in #34687 <details> <summary>🔧 Fixes</summary> - [[iOS] UICollectionView ScrollToTop does not work](#19866) </details> - [iOS] Fixed CollectionView Scroll Jitter for TextType HTML Labels by @SubhikshaSf4851 in #34383 <details> <summary>🔧 Fixes</summary> - [CollectionView scrolling is jittery when ItemTemplate contains Label with TextType="Html" in .NET 10](#33065) </details> - Fix CollectionView Header is not visible when ItemsSource is not set and an EmptyView is set in iOS, Mac platform by @KarthikRajaKalaimani in #34989 <details> <summary>🔧 Fixes</summary> - [CollectionView Header is not visible when ItemsSource is not set and EmptyView is set in iOS, Mac platform](#34897) </details> - [Android] Fix CollectionView EmptyView not displayed correctly by @KarthikRajaKalaimani in #34956 <details> <summary>🔧 Fixes</summary> - [[Android] CollectionView - EmptyView not displayed correctly](#34861) </details> - [iOS] Fix CollectionView ScrollOffset not resetting when ItemsSource changes by @SyedAbdulAzeemSF4852 in #34488 <details> <summary>🔧 Fixes</summary> - [[IOS] CollectionView ScrollOffset does not reset when the ItemSource is changed in iOS.](#26366) - [Re-enable Issue7993 test on iOS/Catalyst - CollectionView scroll position not reset when updating ItemsSource](#33500) </details> - [Revert] [iOS] Fixed CollectionView Scroll Jitter for TextType HTML Labels by @SubhikshaSf4851 in #35341 ## Core Lifecycle - [Android] Fix NRE in ContainerView when Android Context is null during lifecycle transition by @rmarinho in #34901 <details> <summary>🔧 Fixes</summary> - [[Android] NullReferenceException in NavigationRootManager.Connect when mapping Window content](#34900) </details> ## DateTimePicker - [Android] Fix for TimePicker Dialog doesn't update the layout when rotating the device with dialog open by @HarishwaranVijayakumar in #31910 <details> <summary>🔧 Fixes</summary> - [[Android] TimePicker Dialog doesn't update the layout when rotating the device with dialog open](#31658) </details> - [Android, iOS] Fixed TimePicker FlowDirection Not Applied Across Platforms by @Dhivya-SF4094 in #30369 <details> <summary>🔧 Fixes</summary> - [TimePicker FlowDirection Not Working on All Platforms](#30192) </details> - [Windows] Fixed TimePicker CharacterSpacing issue by @SubhikshaSf4851 in #30533 <details> <summary>🔧 Fixes</summary> - [[Windows] TimePicker CharacterSpacing Property Not Working on Windows](#30199) </details> - [MacCatalyst] Fix DatePicker Opened/Closed events not being raised by @SubhikshaSf4851 in #34970 <details> <summary>🔧 Fixes</summary> - [[MacCatalyst] DatePicker Opened and Closed events are not raised on Mac platform](#34848) </details> ## Dialogalert - [Android] Fix AlertDialog, ActionSheet, and Prompt render with Material 2 styles when Material 3 is enabled by @HarishwaranVijayakumar in #35121 <details> <summary>🔧 Fixes</summary> - [[Android] AlertDialog, ActionSheet, and Prompt render with Material 2 styles when Material 3 is enabled](#35119) </details> ## Docs - docs: Add UITesting-Guide, ReleasePlanning, and ReleaseProcess to docs/README.md index by @PureWeen in #35195 - docs: Fix hardcoded path and add library overview in Essentials.AI README by @PureWeen in #35194 - docs: Update branch reference from net10.0 to net11.0 in DEVELOPMENT.md by @PureWeen in #35193 ## Drawing - Fix Path Rendering Issue Inside StackLayout When Margin Is Set by @Shalini-Ashokan in #28071 <details> <summary>🔧 Fixes</summary> - [Path does not render if it has Margin](#13801) </details> - Fixed FlowDirection property not working on Drawable control and GraphicsView by @Dhivya-SF4094 in #34557 <details> <summary>🔧 Fixes</summary> - [[Android, Windows, iOS, macOS] FlowDirection property not working on BoxView Control](#34402) </details> - [iOS & Mac] Fix image tile misalignment in GraphicsView ImagePaint by @SubhikshaSf4851 in #34935 <details> <summary>🔧 Fixes</summary> - [[iOS] Image resized with ResizeMode.Fit is not rendered correctly in GraphicsView](#34755) </details> - Fix Shadow does not honour Styles by @KarthikRajaKalaimani in #35081 <details> <summary>🔧 Fixes</summary> - [Shadow does not honour Styles](#19560) </details> ## Entry - [iOS/macCatalyst] Fix Entry and Editor BackgroundColor reset when set to null by @Shalini-Ashokan in #34741 <details> <summary>🔧 Fixes</summary> - [[iOS, Maccatalyst] Entry & Editor BackgroundColor not reset to Null](#34611) </details> - [Windows] Fix password Entry crash when setting text on empty field by @praveenkumarkarunanithi in #33891 <details> <summary>🔧 Fixes</summary> - [[WinUI] Password Obfuscation causes unhandled crash](#33334) </details> ## Essentials - [Essentials] Use mean sea level altitude on Android API 34+ by @KitKeen in #35097 <details> <summary>🔧 Fixes</summary> - [Add support for MslAltitudeMeters in Essentials Geolocation on Android](#27554) </details> ## Flyout - Fixed Flyout Not Displayed on Android When FlyoutWidth Is Set Only for Desktop via OnIdiom by @NanthiniMahalingam in #29028 <details> <summary>🔧 Fixes</summary> - [[Android] FlyoutWidth with OnIdiom shows no flyout](#13243) </details> - Revert "[Windows] Fix Flyout/Locked mode header collapse regression causing UI test failures on candidate branch" by @kubaflo in #35339 - Revert "Revert "[Windows] Fix Flyout/Locked mode header collapse regression causing UI test failures on candidate branch"" by @kubaflo in #35342 ## Flyoutpage - Fix [Android] Title of FlyOutPage is not updating anymore after showing a NonFlyOutPage by @KarthikRajaKalaimani in #34839 <details> <summary>🔧 Fixes</summary> - [[Android] Title of FlyOutPage is not updating anymore after showing a NonFlyOutPage](#33615) </details> ## Label - [iOS] Fix span Tap gesture on wrapped Label lines in iOS 26+ by @SubhikshaSf4851 in #34640 <details> <summary>🔧 Fixes</summary> - [[iOS]Span TapGestureRecognizer does not work on the second line of the span, if the span is wrapped to the next line](#34504) </details> ## Layout - Fixed Stacklayout is not rendered when clip is applied and StackLayout placed child to the Border control in iOS/ Mac platform by @KarthikRajaKalaimani in #33330 <details> <summary>🔧 Fixes</summary> - [[Mac/iOS] StackLayout fails to render content while applying Clip, and the layout is placed inside a Border with Background in .NET MAUI](#33241) </details> ## Map - Fix Changing Location on a Pin does nothing by @NirmalKumarYuvaraj in #30201 <details> <summary>🔧 Fixes</summary> - [[Maps] [Regression from Xamarin.Forms.Maps] Changing Location on a Pin does nothing](#12916) </details> ## Mediapicker - [iOS] Fix HEIC images picked via PickPhotosAsync not displayed by @HarishwaranVijayakumar in #34954 <details> <summary>🔧 Fixes</summary> - [[iOS] [Regression] HEIC images picked via PickPhotosAsync not displayed](#34953) </details> - [Android] Fix MediaPicker.PickPhotosAsync UnauthorizedAccessException on API 28 and below by @HarishwaranVijayakumar in #34981 <details> <summary>🔧 Fixes</summary> - [MediaPicker.PickPhotos fails to modify image, tries to load original source, fails to load source on Android 9.0](#34889) </details> ## Pages - [iOS] Fix ContentPage with ToolbarItem Clicked event leaks when presented as modal page by @devanathan-vaithiyanathan in #35009 <details> <summary>🔧 Fixes</summary> - [ContentPage with ToolbarItem Clicked event leaks when presented as modal page](#34892) </details> ## Platform - [Android] Fix OnBackButtonPressed not invoked for Shell by @Dhivya-SF4094 in #35150 <details> <summary>🔧 Fixes</summary> - [On Screen Back Button Does Not Fire OnBackButtonPressed in Android](#9095) </details> ## RadioButton - Fix RadioButtonGroup not working with ContentView by @Dhivya-SF4094 in #34781 <details> <summary>🔧 Fixes</summary> - [RadioButtonGroup not working with ContentView](#34759) </details> - [Windows] Fix for RadioButton BorderColor and BorderWidth not updated at runtime by @SyedAbdulAzeemSF4852 in #28335 <details> <summary>🔧 Fixes</summary> - [RadioButton Border color not working for focused visual state](#15806) </details> - [iOS] Fix RadioButton BackgroundColor bleeding outside CornerRadius by @SyedAbdulAzeemSF4852 in #34844 <details> <summary>🔧 Fixes</summary> - [[iOS] RadioButton BackgroundColor bleeds outside CornerRadius](#34842) </details> ## SafeArea - [iOS] Fix stale bottom safe area after changing SafeAreaEdges with keyboard open by @praveenkumarkarunanithi in #35083 <details> <summary>🔧 Fixes</summary> - [[iOS] ContentPage bottom has white space after changing SafeAreaEdges while keyboard is open](#34846) </details> ## ScrollView - [Windows] Fix Preserve ScrollView offsets when Orientation changes to Neither by @SubhikshaSf4851 in #34827 <details> <summary>🔧 Fixes</summary> - [[Windows] ScrollView offsets do not preserve when Orientation changes to Neither](#34671) </details> ## Searchbar - [iOS] Fix SearchBar unexpected left margin in iPad windowed mode on 26 Version by @SubhikshaSf4851 in #34704 <details> <summary>🔧 Fixes</summary> - [in iPad windowed mode SearchBar adds left margin equivaltent to SafeAreaInsets when placed inside grid](#34551) </details> ## Shell - [Windows] Fix for Shell.FlyoutBehavior="Flyout" forces the title height space above the tab bar even if the page title is empty by @BagavathiPerumal in #30382 <details> <summary>🔧 Fixes</summary> - [(Windows) Shell.FlyoutBehavior="Flyout" forces the title height space above the tab bar even if the page title is empty](#30254) </details> - Fix Shell flyout items scrolling behind FlyoutHeader on iOS by @Qythyx in #34936 <details> <summary>🔧 Fixes</summary> - [Shell flyout items scroll behind FlyoutHeader on iOS](#34925) </details> - [iOS, Mac] Fix Shell.CurrentState.Location stale in OnNavigated after GoToAsync by @Vignesh-SF3580 in #34880 <details> <summary>🔧 Fixes</summary> - [Shell.OnNavigated not called for route navigation](#34662) </details> - [iOS26]Fix BackButtonBehavior_IsEnabled_False_BackButtonDoesNotNavigate UITest fails by @devanathan-vaithiyanathan in #34890 <details> <summary>🔧 Fixes</summary> - [[iOS 26] BackButtonBehavior_IsEnabled_False_BackButtonDoesNotNavigate test fails with TimeoutException](#34771) </details> - [iOS] Fix Shell page memory leak when using TitleView with x:Name by @Shalini-Ashokan in #35082 <details> <summary>🔧 Fixes</summary> - [[iOS] Title view memory leak](#34975) </details> - [Material 3] Fix Material 2 color flash in AppBar when switching tabs for the first time by @Dhivya-SF4094 in #35117 <details> <summary>🔧 Fixes</summary> - [Material 3: AppBar briefly displays Material 2 colors when switching tabs for the first time](#35116) </details> - [Android] Fix Shell/TabbedPage "More" BottomSheet uses hard-coded M2 colors when Material3 is enabled by @HarishwaranVijayakumar in #35129 <details> <summary>🔧 Fixes</summary> - [[Android] Shell/TabbedPage "More" BottomSheet uses hard-coded M2 colors when Material3 is enabled](#35127) </details> - [Android] Shell: Fix top-tab unselected text visibility in Material 3 light theme by @SyedAbdulAzeemSF4852 in #35128 <details> <summary>🔧 Fixes</summary> - [[Android] Shell top-tab unselected text appears too faint in Material 3 light theme](#35125) </details> - Fix Shell.Items.Clear() memory leak by disconnecting child handlers on removal (#34898) by @Shalini-Ashokan in #35031 <details> <summary>🔧 Fixes</summary> - [Shell.Items.Clear() does not disconnect handlers correctly](#34898) </details> - [iOS&Mac] Fix Shell SearchHandler Query update on Initial load by @SubhikshaSf4851 in #35008 <details> <summary>🔧 Fixes</summary> - [[iOS&Mac] Shell SearchHandler Query not shown in search bar on initial load](#35005) </details> ## SwipeView - [iOS,MacCatalyst] Fix for SwipeView.Open() throwing an ArgumentException on the second programmatic call by @BagavathiPerumal in #34982 <details> <summary>🔧 Fixes</summary> - [[net 11.0][iOS,MacCatalyst] SwipeView.Open() throws ArgumentException on second programmatic call](#34917) </details> - [Android/iOS] Fix SwipeItem visibility change causing double command execution in Execute mode by @praveenkumarkarunanithi in #35087 <details> <summary>🔧 Fixes</summary> - [Changing visibility on an SwipeItem causes multiple items to be executed](#7580) </details> ## Switch - [iOS] Fix Switch ThumbColor reset on iOS 26+ theme changes. by @Shalini-Ashokan in #33953 <details> <summary>🔧 Fixes</summary> - [Switch ThumbColor not Initialized Using VisualStateManager on iOS Device](#33783) - [I9-On macOS 26.2, the "Animate scroll" button is white by default on iOS and Maccatalyst platforms.](#33767) </details> ## TabbedPage - [Windows] TabbedPage: Refresh layout when NavigationView size changes by @BagavathiPerumal in #26217 <details> <summary>🔧 Fixes</summary> - [TabbedPage - ScrollView not allowing scrolling when it should](#26103) - [TabbedPage App on resize hides page bottom content](#11402) - [Grid overflows child ContentPage of parent TabbedPage on initial load and when resizing on Windows](#20028) </details> - [Android] Material 3 Fixed BottomNavigationView overflowing in Tabbed page by @NirmalKumarYuvaraj in #35064 <details> <summary>🔧 Fixes</summary> - [[Android] Material3 - TabbedPage bottom tabs overflowing the contents](#35063) </details> - [Windows] Fix for Multiple Tabs Being Selected in WinUI TabbedPage by @SyedAbdulAzeemSF4852 in #33312 <details> <summary>🔧 Fixes</summary> - [WinUI TabbedPage can have multiple tabs selected](#31799) </details> ## Theming - [iOS] Fix StaticResource Hot Reload crash on iOS by @StephaneDelcroix in #35020 <details> <summary>🔧 Fixes</summary> - [The maui app quit and no errors in error list after editing ResourceDictionary XAML file on iOS Simulator with MAUI SR6 10.0.60](#35018) </details> ## Toolbar - [Windows] Fix for CS1061 build error caused by missing HasMenuBarContent property in MauiToolbar by @BagavathiPerumal in #35040 ## Tooling - Fix VisualStateGroups duplicate name crash with implicit styles (#34716) by @StephaneDelcroix in #34719 <details> <summary>🔧 Fixes</summary> - [SourceGen: VisualStateManager.VisualStateGroups causes 'Names must be unique' at startup](#34716) </details> ## WebView - Refactor the HybridWebView and properly support complex parameters by @mattleibow in #32491 - [Android] Fix WebView scrolling inside ScrollView by @Shalini-Ashokan in #33133 <details> <summary>🔧 Fixes</summary> - [[Android] WebView's content does not scroll when placed inside a ScrollView](#32971) </details> <details> <summary>🔧 Infrastructure (1)</summary> - [Windows] Fix Narrator announcing ContentView children twice when Description is set by @praveenkumarkarunanithi in #33979 <details> <summary>🔧 Fixes</summary> - [[Windows] SemanticProperties.Description announced twice when set on focusable container cell (Label inside)](#33373) </details> </details> <details> <summary>🧪 Testing (14)</summary> - [Testing] SafeArea Feature Matrix Test Cases for ContentPage by @TamilarasanSF4853 in #34877 - [Windows] Fix CollectionView ScrollTo related test cases failed in CI by @HarishwaranVijayakumar in #34907 <details> <summary>🔧 Fixes</summary> - [[Testing][Windows]CollectionView ScrollTo related test cases failed in CI](#34772) </details> - [Testing] Fixed Build error on inflight/ candidate PR 35234 by @HarishKumarSF4517 in #35241 - Fix CI for ValidateKeyboardRuntime_SwitchContainerToSoftInput_WhileKeyboardOpen test failure in May 4th Candidate by @devanathan-vaithiyanathan in #35307 - [Windows] Fix Flyout/Locked mode header collapse regression causing UI test failures on candidate branch by @BagavathiPerumal in #35312 - [iOS/macCatalyst] [Candidate Fix] Editor shadow and theme regression caused by BackgroundColor reset on initial handler connection by @Shalini-Ashokan in #35343 - [Testing] Fixed UI test image failure in PR 35234 - [30/03/2026] Candidate - 1 by @HarishKumarSF4517 in #35325 - [iOS] Fix ShellFeatureMatrix test failures on candidate branch by @Vignesh-SF3580 in #35346 - [Windows] Fix Issue29529VerifyPreviousPositionOnInsert test failure on candidate branch by @praveenkumarkarunanithi in #35398 - [Android] [Candidate Fix] Shell: Fix handler disconnect timing to preserve WebView navigation and memory leak fix by @Shalini-Ashokan in #35417 - [Testing]Revert 'Fix Preserve ScrollView offsets when Orientation changes to Neither' by @TamilarasanSF4853 in #35412 - [Windows] Fix VerifyAllIndicatorDotsShowShadowsWhenIndicatorSize test failure on candidate branch by @praveenkumarkarunanithi in #35458 - [Testing] Fixed test failure in PR 35234 - [05/08/2026] Candidate by @TamilarasanSF4853 in #35362 - [Testing] Fixed test failure in PR 35234 - [05/04/2026] Candidate - 3 by @TamilarasanSF4853 in #35639 </details> <details> <summary>📦 Other (6)</summary> - [UIKit] Avoid useless measure invalidation propagation cycles by @albyrock87 in #33459 - BindableObject property access micro-optimizations by @albyrock87 in #33584 - Extract filename from DisplayName and add extension if missing by @mattleibow in #35050 - [core] Add keyed-DI screenshot extensibility for 3rd-party platform backends by @Redth in #35096 <details> <summary>🔧 Fixes</summary> - [`ViewExtensions.CaptureAsync(IView)` and `IPlatformScreenshot` need extensibility for third-party platform backends](#34266) </details> - Fix MainThread throwing on custom platform backends by @Redth in #35070 <details> <summary>🔧 Fixes</summary> - [`MainThread.BeginInvokeOnMainThread` throws on custom platform backends - Common UI-thread marshaling pattern crashes; `Dispatcher` works but isn't the documented/recommended path](#34101) </details> - Tests: Add 11 missing UnitConverters unit tests by @PureWeen in #35191 </details> <details> <summary>📝 Issue References</summary> Fixes #7580, Fixes #9095, Fixes #11402, Fixes #12916, Fixes #13243, Fixes #13801, Fixes #15806, Fixes #19560, Fixes #19690, Fixes #19866, Fixes #20028, Fixes #26103, Fixes #26366, Fixes #27554, Fixes #29529, Fixes #29772, Fixes #30192, Fixes #30199, Fixes #30254, Fixes #31658, Fixes #31799, Fixes #32971, Fixes #33065, Fixes #33241, Fixes #33334, Fixes #33373, Fixes #33500, Fixes #33615, Fixes #33767, Fixes #33783, Fixes #34101, Fixes #34257, Fixes #34266, Fixes #34402, Fixes #34504, Fixes #34551, Fixes #34611, Fixes #34662, Fixes #34671, Fixes #34716, Fixes #34755, Fixes #34759, Fixes #34771, Fixes #34772, Fixes #34842, Fixes #34846, Fixes #34848, Fixes #34861, Fixes #34889, Fixes #34892, Fixes #34897, Fixes #34898, Fixes #34900, Fixes #34917, Fixes #34925, Fixes #34953, Fixes #34975, Fixes #35005, Fixes #35018, Fixes #35063, Fixes #35116, Fixes #35119, Fixes #35125, Fixes #35127 </details> **Full Changelog**: main...inflight/candidate
Note
Are you waiting for the changes in this PR to be merged?
It would be very helpful if you could test the resulting artifacts from this PR and let us know in a comment if this change resolves your issue. Thank you!
Root Cause
WinUI
TextBlock(used byLabel) automatically exposes itsTextto UI Automation.ContentPaneluses the defaultFrameworkElementAutomationPeer, which exposes both the parent’sAutomationProperties.Nameand all child elements. Unlike Android (NoHideDescendants) or iOS (AccessibilityElementsHidden), Windows has no single property to hide descendants while keeping the parent accessible. As a result, both Tab navigation and Browse mode announced duplicate content.Description of Change
Implemented a custom
ContentPanelAutomationPeerthat overrides three core UI Automation methods (GetAutomationControlTypeCore ,GetLocalizedControlTypeCore,GetChildrenCore) to conditionally modify behavior when Description is present.When a Description exists, the control is exposed as
AutomationControlType.Text(enables browse mode navigation; alternatives like Custom announce "custom" suffix, Group causes browse mode to skip the element), the "text" announcement suffix is suppressed via emptyGetLocalizedControlTypeCore()return, and child elements are hidden by returning null fromGetChildrenCore()to prevent duplication. When no Description is present, default behavior is preserved withAutomationControlType.Customand children remain accessible.The
HasDescriptionhelper property centralizes the non-empty Description check across all three override methods, ensuring consistent conditional logic following the MAUI `AutomationPeer patterns.Issues Fixed
Fixes #33373
Platforms Tested
Screenshots
WITHOUTFIX.1.mp4
WITHFIX.1.1.mp4