Accessibility
DrawnUI renders controls into a Skia surface instead of creating a native control tree. Accessibility therefore needs a parallel virtual representation that assistive technology can read and activate.
Drawn controls do not have accessibility turned on by default on purpose to let you cotrol which parts will be exposed and how.
Current Support
| Framework / target | Status | Implementation | Notes |
|---|---|---|---|
| Blazor | Available | Invisible ARIA overlay positioned over the canvas | Accessible today, with one important hover limitation described below |
| OpenTK Windows | Incoming | UIA provider bridge on the native OpenTK window | Planned, not shipped yet |
| OpenTK Linux | Incoming | AT-SPI bridge on the native OpenTK window | Planned, not shipped yet |
| .NET MAUI iOS / macCatalyst | Incoming | Virtual UIAccessibilityElement container |
Planned, not shipped yet |
| .NET MAUI Android | Incoming | Virtual nodes via ExploreByTouchHelper |
Planned, not shipped yet |
| .NET MAUI Windows | Incoming | Virtual UIA fragment providers | Planned, not shipped yet |
All targets share the same C# accessibility metadata on SkiaControl. Platform-specific layers consume the SkiaAccessibilityManager snapshot and expose it through the native accessibility API for that platform.
Shared Model
Accessibility starts in shared code. A drawn control can expose:
- role
- label
- hint
- whether it can interact
- pressed / toggle state
That metadata is collected by SkiaAccessibilityManager, which maintains a snapshot of accessible nodes and their bounds in UI coordinates.
Accessibility Props
Accessibility metadata is exposed directly on SkiaControl.
control.AccessibilityRole = Aria.RoleButton;
control.AccessibilityLabel = "Save";
control.AccessibilityHint = "Saves the document";
control.AccessibilityCanInteract = true;
control.AccessibilityIsPressed = false;
AccessibilityRoleenables accessibility for the controlAccessibilityLabelis the main spoken labelAccessibilityHintgives extra context for assistive technologyAccessibilityCanInteractmarks the node as interactiveAccessibilityIsPressedmaps toggle state when applicable
IsAccessibilityElement is computed from AccessibilityRole != null. Setting the role back to null removes the control from the accessibility tree.
Can set them from code-behind or XAML where it is supported.
Fluent Code-Behind Methods
The same metadata can be attached with fluent helpers.
// General
.WithAccessibility(string role, string? label = null, string? hint = null, bool canInteract = false)
.WithAccessibility(string role, string? label = null, bool canInteract = false)
// Common shortcuts
.WithAccessibilityButton(string label, string? hint = null)
.WithAccessibilityButton(string label)
.WithAccessibilityButton()
.WithAccessibilityText(string text)
.WithAccessibilityText()
// Toggle state
.WithAccessibilityPressed(bool? pressed)
.WithAccessibilityToggle(string label, string? hint = null)
Example:
new GameSwitch()
.WithAccessibilityToggle(ResStrings.Sounds);
WithAccessibilityToggle keeps AccessibilityIsPressed in sync with toggle state, which is important for screen readers announcing switches and similar controls.
Implementation in deep
Blazor
In Blazor, DrawnUI renders the canvas as usual and also renders an invisible DOM overlay for accessibility.
- The visible canvas surface is marked
aria-hidden. - A sibling overlay contains absolutely positioned ARIA elements that mirror the drawn controls.
- Interactive accessibility nodes can receive keyboard focus and activation.
This gives screen readers a DOM-based accessibility surface even though the real UI is drawn.
IMPORTANT: on Blazor accessibility overlay and canvas hover on the same control are mutually exclusive. if you add accessibility metadata to a drawn control it will stop receiving Pointer gestures and will not be able to react to hover, those will be catched by a corresponding accessibility DOM element. Other gestures will work as usual.