maui-accessibility
.NET MAUI Accessibility Skill
Use this skill when adding or auditing accessibility in .NET MAUI apps. It covers SemanticProperties, AutomationProperties, screen reader announcements, and platform-specific pitfalls.
SemanticProperties (Attached Properties)
Set on any VisualElement to provide screen reader context.
| Property | Purpose |
|---|---|
SemanticProperties.Description |
Accessible name read by screen reader |
SemanticProperties.Hint |
Extra context (e.g. "Double tap to activate") |
SemanticProperties.HeadingLevel |
Heading landmark: None, Level1–Level9 |
XAML
<Button Text="Save"
SemanticProperties.Description="Save your changes"
SemanticProperties.Hint="Double tap to save the current document" />
<Label Text="Settings"
SemanticProperties.HeadingLevel="Level1" />
C#
var btn = new Button { Text = "Save" };
SemanticProperties.SetDescription(btn, "Save your changes");
SemanticProperties.SetHint(btn, "Double tap to save the current document");
var heading = new Label { Text = "Settings" };
SemanticProperties.SetHeadingLevel(heading, SemanticHeadingLevel.Level1);
Programmatic Focus & Announcements
SetSemanticFocus
Move screen reader focus to an element after a UI change:
errorLabel.Text = "Username is required";
errorLabel.SetSemanticFocus();
SemanticScreenReader.Announce
Push a live announcement without moving focus:
SemanticScreenReader.Announce("File uploaded successfully");
Use Announce for transient status updates. Use SetSemanticFocus when the user
must interact with the target element.
AutomationProperties
Control whether an element appears in the accessibility tree.
| Property | Effect |
|---|---|
AutomationProperties.IsInAccessibleTree |
false hides the element from screen readers |
AutomationProperties.ExcludedWithChildren |
true hides the element and all descendants |
<!-- Decorative image — hide from screen reader -->
<Image Source="bg.png"
AutomationProperties.IsInAccessibleTree="false" />
<!-- Container with purely decorative content -->
<Grid AutomationProperties.ExcludedWithChildren="true">
<Image Source="pattern.png" />
</Grid>
Deprecated AutomationProperties → SemanticProperties
| Deprecated | Replacement |
|---|---|
AutomationProperties.Name |
SemanticProperties.Description |
AutomationProperties.HelpText |
SemanticProperties.Hint |
Avoid the deprecated properties in new code. They may not work consistently across platforms and will be removed in a future release.
Critical Platform Gotchas
1. Don't set Description on Label
Setting SemanticProperties.Description on a Label overrides the Text
property for screen readers. If Description matches Text, the label may be read
twice or behave unexpectedly. Omit Description and let the screen reader read
Text directly.
<!-- BAD — stops Text from being read naturally -->
<Label Text="Welcome"
SemanticProperties.Description="Welcome" />
<!-- GOOD — screen reader reads Text automatically -->
<Label Text="Welcome" />
2. Android Entry/Editor: Description breaks TalkBack actions
On Android, setting SemanticProperties.Description on Entry or Editor
causes TalkBack to lose "double tap to edit" action hints. Use Placeholder or
SemanticProperties.Hint instead.
<!-- BAD on Android -->
<Entry SemanticProperties.Description="Email address" />
<!-- GOOD — use Placeholder for context -->
<Entry Placeholder="Email address" />
3. iOS: Description on parent hides children
On iOS/VoiceOver, setting SemanticProperties.Description on a layout (e.g.
StackLayout, Grid) makes the entire container a single accessible element,
making child elements unreachable.
<!-- BAD — children invisible to VoiceOver -->
<HorizontalStackLayout SemanticProperties.Description="User info">
<Label Text="Name:" />
<Label Text="Alice" />
</HorizontalStackLayout>
<!-- GOOD — let children be individually focusable -->
<HorizontalStackLayout>
<Label Text="Name:" />
<Label Text="Alice" />
</HorizontalStackLayout>
4. Hint conflicts with Entry.Placeholder on Android
On Android, SemanticProperties.Hint and Entry.Placeholder map to the same
Android attribute (contentDescription / hint). Setting both may cause one to
override the other. Choose one.
5. HeadingLevel platform differences
- Windows (Narrator): Supports all 9 heading levels (
Level1–Level9). - Android (TalkBack) / iOS (VoiceOver): Only distinguish "heading" vs
"not heading". All levels (
Level1–Level9) are treated identically.
Use Level1–Level9 for semantic correctness; just know the hierarchy only
renders on Windows.
Accessibility Checklist for Existing Pages
When auditing or retrofitting a page, work through this list:
- Images: Add
SemanticProperties.Descriptionto meaningful images. SetAutomationProperties.IsInAccessibleTree="false"on decorative ones. - Buttons/Controls: Ensure icon-only buttons have
Description. Text buttons generally don't need it. - Entries/Editors: Use
Placeholderfor context. AddHintonly if extra instruction is needed. AvoidDescription(Android breakage). - Labels: Do not add
Description— letTextspeak for itself. AddHeadingLevelto section headers. - Headings: Mark page title as
Level1, section titles asLevel2, etc. - Grouping: Avoid
Descriptionon layout containers (iOS breakage). UseExcludedWithChildrento hide decorative groups. - Dynamic content: Call
SemanticScreenReader.Announcefor status changes. UseSetSemanticFocusafter navigation or error display. - Tab order: Set
TabIndexon interactive controls for logical order. - Test: Run TalkBack (Android), VoiceOver (iOS/Mac), and Narrator (Windows) to verify the reading order and actions.