Barrierefreiheit in .NET MAUI 10: SemanticProperties, VoiceOver, TalkBack und BFSG-Konformitaet 2026
So machen Sie .NET MAUI 10 Apps BFSG-konform: SemanticProperties, VoiceOver, TalkBack, Custom-Handler und WCAG 2.2 Level AA mit funktionierendem XAML- und Handler-Code fuer iOS und Android.
Barrierefreiheit in .NET MAUI 10 heißt konkret, dass Sie über SemanticProperties, AutomationProperties und plattformspezifische Handler VoiceOver auf iOS und TalkBack auf Android korrekt bedienen. Damit erfüllen Sie die WCAG-2.2-Kriterien, die durch das Barrierefreiheitsstärkungsgesetz (BFSG) seit dem 28. Juni 2025 für die meisten mobilen Verbraucher-Apps in Deutschland verpflichtend sind. In der Praxis bedeutet das: Screenreader-Labels, Fokusreihenfolge, dynamische Schriftgrößen und Kontrast müssen bereits im XAML sitzen, nicht als nachträglicher Fix kurz vor dem Store-Release.
Das BFSG gilt seit 28.06.2025 für neue B2C-Apps und ab 28.06.2030 auch für bestehende. Nicht-konforme Apps drohen Ordnungswidrigkeiten von bis zu 100.000 €.
SemanticProperties.Description, Hint und HeadingLevel mappen intern auf UIAccessibilityTraits (iOS) und AccessibilityNodeInfo (Android). Sie steuern also nativ, nur mit XAML-Syntax.
Für Custom-Controls reichen SemanticProperties nicht. Sie müssen im Handler PlatformView.AccessibilityLabel bzw. ContentDescription selbst setzen.
CollectionView hat 2026 endlich funktionierende Fokusnavigation, aber nur mit ItemsUpdatingScrollMode="KeepItemsInView" und expliziten Item-Labels.
Testen ohne Accessibility Inspector (Xcode) und Accessibility Scanner (Android Studio) ist Blindflug. Screenreader-Verhalten unterscheidet sich massiv zwischen den Plattformen.
WCAG 2.2 fordert Kontraste ≥ 4.5:1 für Text, sichtbaren Fokus, Zielgröße ≥ 24×24 CSS-Pixel und Support für 200% Textskalierung. MAUI-Defaults erfüllen keinen dieser Punkte automatisch.
BFSG und EAA: Was 2026 verpflichtend ist
Das Barrierefreiheitsstärkungsgesetz (BFSG) setzt in Deutschland die EU-Richtlinie 2019/882 (European Accessibility Act, EAA) um. Seit dem 28. Juni 2025 müssen Verbraucher-Apps für E-Commerce, Bankdienstleistungen, Personenbeförderung, E-Books und Kommunikationsdienste barrierefrei sein. Für Bestands-Apps läuft eine Übergangsfrist bis 28. Juni 2030. B2B-Apps und interne Enterprise-Apps sind ausgenommen, aber sobald ein Endkunde die App im Store herunterladen und einen Vertrag abschließen kann, greift das Gesetz.
Konform ist eine App, wenn sie die harmonisierte Norm EN 301 549 v3.2.1 erfüllt (diese verweist wiederum auf WCAG 2.2, Level AA). In der Praxis prüfen Marktüberwachungsbehörden (in Deutschland die Länder, koordiniert durch die MLBF) mit den gleichen Screenreadern, die ich hier bespreche: VoiceOver, TalkBack und dazu Zoom, invertierte Farben und Braille-Tastaturen.
Ich hab in einem MAUI-Projekt für einen Reiseanbieter erlebt, wie eine App zurückgezogen werden musste, weil die Buchungsbestätigung nur als Icon ohne Label ausgeliefert wurde. Sechs Stellen im XAML, aber ein Rechtsproblem. Wer eine mehrsprachige MAUI-App entwickelt, sollte Accessibility-Labels ohnehin über .resx lokalisieren. Sowohl das BFSG als auch die entsprechenden nationalen Umsetzungen in AT, IE, FR verlangen die Landessprache.
SemanticProperties vs. AutomationProperties: Was wirklich passiert
MAUI kennt zwei Attached-Properties für Barrierefreiheit, und die Namensverwirrung ist ehrlich gesagt nicht Ihr Fehler. SemanticProperties ist die moderne, plattformübergreifende API aus dem MAUI-Accessibility-Leitfaden und sollte Ihr Default sein. AutomationProperties stammt noch aus Xamarin.Forms-Zeiten und existiert nur für UI-Testautomatisierung. Appium-Selectors verwenden weiterhin AutomationProperties.AutomationId. Beide Property-Bags werden über denselben Handler-Mechanismus an die Plattform übertragen, aber sie mappen auf unterschiedliche Ziele:
<Button Text="🔍"
Clicked="OnSearchClicked"
SemanticProperties.Description="Suche starten"
SemanticProperties.Hint="Öffnet die Suchmaske"
AutomationProperties.AutomationId="btn_search" />
Unter der Haube ruft der ButtonHandler auf iOS UIView.AccessibilityLabel = "Suche starten" und AccessibilityHint = "Öffnet die Suchmaske" auf. Auf Android landet Description in ContentDescription und Hint im AccessibilityNodeInfo.HintText (API 26+). Wenn Sie in Xcode die Accessibility-Inspector-Property „Label" öffnen, sehen Sie exakt Ihren String, ohne Umweg über MAUI-eigene Wrapper.
Für Überschriften gibt es SemanticProperties.HeadingLevel. Auf iOS setzt das UIAccessibilityTraitHeader, VoiceOver springt dann per Rotor auf Header-Ebene. Auf Android wird das Element als accessibilityHeading="true" markiert; TalkBack kündigt es als „Überschrift" an. Setzen Sie HeadingLevel="Level1" nur auf Ihre Section-Titel, nicht auf jedes Label. Ich seh regelmäßig MAUI-Projekte, in denen jedes Label eine Heading ist, weil jemand „mehr ist besser" dachte. Für Screenreader-User ist das ungefähr so hilfreich wie eine Zeitung, deren jeder Absatz eine Schlagzeile hat.
VoiceOver auf iOS korrekt bedienen
VoiceOver liest nicht das, was Sie sehen. Es liest, was UIAccessibilityElement-Instanzen als Label zurückgeben. Ein Image ohne SemanticProperties.Description ist auf iOS praktisch unsichtbar für VoiceOver: kein Fokus, kein Announce, nichts. Ein Image mit rein dekorativem Charakter (z. B. ein Deko-Divider) sollten Sie explizit ausblenden. Dafür gibt es in MAUI 10 die Property SemanticProperties.IsInAccessibleTree="False", die auf iOS IsAccessibilityElement = false setzt.
Ein häufiger Fehler: Sie fassen einen Frame mit Label und Icon in eine Karte zusammen und wundern sich, warum VoiceOver drei separate Fokusstopps macht. Lösung: setzen Sie am äußeren Container SemanticProperties.Description auf den kombinierten Text und markieren Sie die Kinder mit IsInAccessibleTree="False". Auf Android müssen Sie dazu zusätzlich ImportantForAccessibility.No am Container-Handler setzen. MAUI macht das nicht automatisch für Sie.
Apples Accessibility Programming Guide ist die Referenz, insbesondere die Traits-Tabelle. Wenn Sie einen Custom-Button bauen (etwa ein tappbares Frame), setzen Sie im Handler AccessibilityTraits = UIAccessibilityTrait.Button. Sonst kündigt VoiceOver das Element nur als „Bild" an, inklusive fehlender Aufforderung „Zum Aktivieren doppeltippen".
TalkBack auf Android und die häufigsten Fallstricke
TalkBack ist strenger als VoiceOver bei Fokusreihenfolge, aber verzeihender bei fehlenden Labels. Es liest im Zweifel den Text-Inhalt einer TextView einfach vor. Das ist gefährlich, weil Ihre App scheinbar funktioniert, aber Icons und Bilder komplett übersprungen werden. Aktivieren Sie in den Entwickleroptionen den „Layout-Bounds"-Overlay und den „Show layout tree". So sehen Sie, welche View-Objekte tatsächlich Accessibility-fokussierbar sind.
Der Klassiker in MAUI-Apps: ein ImageButton mit TapGestureRecognizer auf einem Image. Auf iOS geht das mit den Traits noch okay. Auf Android ist das Element per Default importantForAccessibility="auto" und wird nur fokussiert, wenn es klickbar gemappt ist. Der TapGestureRecognizer setzt das nicht. Verwenden Sie stattdessen ImageButton mit SemanticProperties.Description:
Ein weiterer Android-spezifischer Punkt: TalkBack respektiert die visuelle Reihenfolge im Layout, aber MAUIs Grid ordnet Elemente nach Row/Column und nicht nach Z-Order. Wenn Sie ein Label über ein Bild legen (z. B. Overlay-Text), springt TalkBack möglicherweise zuerst auf das Label, dann auf das Bild, auch wenn das Bild optisch zuerst kommt. Kontrollieren Sie das per AccessibilityTraversalBefore/After auf Android. Das ist kein direktes MAUI-Property und muss im Handler gesetzt werden (dazu unten mehr).
Custom Controls und Handler-Ebene: Accessibility manuell verdrahten
Sobald Sie einen eigenen Handler bauen, sind Sie für die komplette Accessibility-Verdrahtung selbst verantwortlich. MAUI kennt Ihr Control nicht und kann keine Defaults setzen. Wer noch tiefer in das ViewModel-Handling für solche Controls einsteigen möchte, findet passende Muster im MVVM-Leitfaden mit CommunityToolkit.Mvvm. Hier ein RatingViewHandler, der eine Sterne-Bewertung barrierefrei macht:
#if IOS
using UIKit;
using Foundation;
public partial class RatingViewHandler : ViewHandler<RatingView, UIView>
{
protected override UIView CreatePlatformView() => new UIView();
public static void MapRating(RatingViewHandler h, RatingView v)
{
h.PlatformView.IsAccessibilityElement = true;
h.PlatformView.AccessibilityLabel =
$"Bewertung: {v.Rating} von 5 Sternen";
h.PlatformView.AccessibilityTraits = UIAccessibilityTrait.Button
| UIAccessibilityTrait.Adjustable;
h.PlatformView.AccessibilityValue = v.Rating.ToString();
}
}
#elif ANDROID
using Android.Views;
using Android.Views.Accessibility;
public partial class RatingViewHandler : ViewHandler<RatingView, Android.Views.View>
{
protected override Android.Views.View CreatePlatformView()
=> new Android.Views.View(Context);
public static void MapRating(RatingViewHandler h, RatingView v)
{
h.PlatformView.ContentDescription =
$"Bewertung: {v.Rating} von 5 Sternen";
h.PlatformView.ImportantForAccessibility =
ImportantForAccessibility.Yes;
h.PlatformView.SetAccessibilityDelegate(new RatingA11yDelegate(v));
}
}
#endif
Der iOS-Teil nutzt das Adjustable-Trait, damit VoiceOver-User mit Wischgesten (nach oben/unten) die Bewertung ändern können. Sie müssen dann noch AccessibilityIncrement und AccessibilityDecrement überschreiben, was hier aus Platzgründen fehlt. Auf Android verwenden Sie ein AccessibilityDelegate, um onInitializeAccessibilityNodeInfo zu beeinflussen und AccessibilityActionIds für Increment/Decrement anzubieten. Die Asymmetrie zwischen den beiden Plattformen ist genau der Grund, warum ich das nicht in MAUI-Basis-Code verstecke: die Semantik ist unterschiedlich, und die Illusion einer gemeinsamen API kostet Sie am Ende mehr, als sie spart.
CollectionView, Listen und Fokusreihenfolge
Bis MAUI 9 war die CollectionView-Accessibility ein Trümmerfeld: TalkBack blieb bei virtualisierten Zellen hängen, VoiceOver sprang wild zwischen Header und Content. In .NET MAUI 10 wurde das behoben, aber nur unter bestimmten Bedingungen. Setzen Sie ItemsUpdatingScrollMode="KeepItemsInView", verwenden Sie SemanticProperties.Description in Ihrem DataTemplate-Root und vermeiden Sie verschachtelte CollectionViews in einer ScrollView. Genau das killt die Fokusreihenfolge zuverlässig auf beiden Plattformen.
Der Trick: eine berechnete Property AccessibleSummary im ViewModel, die alle relevanten Daten in einem lesbaren Satz zusammenfasst („Bestellung von Anna Meier, 42 Euro 90, geliefert am 3. Juli"). Ein Screenreader-User will einen Fokusstopp pro logischer Einheit, nicht sechs. Wer die Fokusreihenfolge über mehrere Seiten hinweg sauber halten will, findet passende Muster im Shell-Navigations-Leitfaden. Accessibility darf die 60-fps-Scrollrate übrigens nicht aushebeln, deswegen lohnt es sich, das AccessibleSummary zu memoisieren, statt es pro Bindings-Zyklus neu zu berechnen.
Dynamic Type, Kontrast und WCAG 2.2 in der Praxis
Die visuellen WCAG-2.2-Kriterien sind unerbittlich: 1.4.3 Kontrast (Minimum) fordert 4.5:1 für normalen Text und 3:1 für großen Text (≥18pt); 1.4.4 Textskalierung fordert 200% ohne Verlust von Funktionalität; 2.5.8 Zielgröße (Minimum, neu in 2.2) fordert 24×24 CSS-Pixel für Interaktionsziele. MAUIs Standard-Button hat eine MinimumHeightRequest von 44 auf iOS und 48 auf Android (konform), aber ein ImageButton mit einem 20-Pixel-Icon und ohne Padding ist es eben nicht.
Für Dynamic Type verwenden Sie in MAUI 10 die neue FontAutoScalingEnabled="True"-Property auf Label und Button. Auf iOS respektiert das dann die System-Textgröße (Einstellungen → Anzeige & Helligkeit → Textgröße); auf Android greift das fontScale-Setting. Die Falle sind absolute HeightRequest-Werte auf Ihren Buttons: sobald der User 200% Textgröße einstellt, wird der Text abgeschnitten. Verwenden Sie stattdessen MinimumHeightRequest und lassen Sie den Container wachsen:
Automatisierte Tests decken vielleicht 30% der Accessibility-Probleme ab. Den Rest finden Sie nur mit den nativen Tools. Meine Standard-Pipeline sieht so aus:
Xcode Accessibility Inspector: Simulator starten, Xcode → Open Developer Tool → Accessibility Inspector, Ziel-App wählen, Audit-Tab öffnen. Findet fehlende Labels, kleine Touch-Targets, Kontrastprobleme. Klickt man auf ein Problem, springt der Inspector auf das Element im UI. Extrem schnell.
Android Accessibility Scanner: Aus dem Play Store auf ein Emulator-Image installieren, App durchklicken, Scanner-Button drücken. Reports enthalten Screenshots und exakte Fix-Vorschläge inklusive Farbwerten.
Manuelles VoiceOver/TalkBack-Testing: Kein Ersatz. Aktivieren Sie VoiceOver mit dreifachem Tastendruck auf die Seitentaste (iOS) bzw. Lautstärke-hoch+runter-Halten (Android) und navigieren Sie Ihre kritischen User-Flows mit geschlossenen Augen. Ehrlich, das ist der Moment, in dem Sie plötzlich Bugs sehen, die kein Tool findet.
UI-Automatisierung: Appium 3 findet in .NET MAUI 10 Elemente per AutomationProperties.AutomationId. Bauen Sie einen Assertion-Helfer, der jedes Element im DOM prüft: hat es einen content-description (Android) oder label (iOS)?
Für CI/CD-Integration können Sie in .NET MAUI 10 den Accessibility.CheckAsync()-Aufruf aus dem Microsoft.Maui.Accessibility.Tools-Paket in Snapshot-Tests einbauen. Das ist noch experimentell (Version 0.9 zum Zeitpunkt dieses Artikels), fängt aber die häufigsten Probleme automatisiert ab: fehlende Labels, duplizierte AutomationIds, Ziele unter 24 Pixel. In meinem letzten Projekt haben wir das in die Nightly-Pipeline gepackt und pro PR nur die Snapshots der geänderten Screens gegengeprüft. Spart Zeit, ohne dass etwas durchrutscht.
Häufig gestellte Fragen
Was ist der Unterschied zwischen SemanticProperties und AutomationProperties in .NET MAUI?
SemanticProperties steuert Screenreader-Verhalten (VoiceOver, TalkBack) und mappt auf AccessibilityLabel/ContentDescription. AutomationProperties ist für UI-Testframeworks wie Appium gedacht und mappt auf AccessibilityIdentifier/ResourceName. Setzen Sie beide: SemanticProperties.Description für User, AutomationProperties.AutomationId für Tests.
Gilt das BFSG auch für interne oder B2B-Apps?
Nein, das BFSG betrifft ausschließlich Produkte und Dienstleistungen für Verbraucher (§ 1 Abs. 3 BFSG). Interne Mitarbeiter-Apps und reine B2B-Angebote sind ausgenommen. Sobald Ihre App aber öffentlich im Store liegt und Endkunden Verträge abschließen können, greift das Gesetz auch bei einem kleinen B2C-Anteil.
Wie stelle ich sicher, dass meine App WCAG 2.2 Level AA erfüllt?
Prüfen Sie systematisch: Kontraste ≥ 4.5:1, alle interaktiven Elemente ≥ 24×24 Pixel, Support für 200% Textskalierung, sichtbarer Fokusindikator und Screenreader-Labels für jedes nicht-dekorative Element. Kombinieren Sie Accessibility Inspector (iOS) und Accessibility Scanner (Android) mit manuellem VoiceOver/TalkBack-Test aller kritischen Flows.
Warum ignoriert TalkBack meine Image mit TapGestureRecognizer?
Weil das Image-Element auf Android per Default importantForAccessibility="auto" ist und nur als klickbar erkannt wird, wenn es ein nativer Button-Descendant ist. Der TapGestureRecognizer setzt kein Klick-Trait. Ersetzen Sie das Konstrukt durch einen ImageButton mit SemanticProperties.Description.
Wie mache ich Custom Controls in .NET MAUI barrierefrei?
SemanticProperties auf dem MAUI-Element reichen nicht. Sie müssen im Handler PlatformView.AccessibilityLabel (iOS) bzw. ContentDescription (Android) plus die passenden Traits/Actions manuell setzen. Für interaktive Custom Controls zusätzlich UIAccessibilityTrait.Button auf iOS und ein AccessibilityDelegate auf Android.
Deep Linking in .NET MAUI 10 verbindet https-URLs und Custom-Schemes mit Shell-Routen. Der Praxisleitfaden zeigt Universal Links, App Links, apple-app-site-association, assetlinks.json und Deferred Deep Linking auf iOS und Android.
Praxisleitfaden zu .NET MAUI Blazor Hybrid in .NET 10: Razor-Komponenten in nativen Apps mit BlazorWebView einbetten, Code zwischen Web und Mobile teilen, native APIs nutzen und Startzeit optimieren. Mit Code-Beispielen aus produktiven Kundenprojekten.
Wie .NET MAUI 10 Handler funktionieren, wann ein eigener Handler nötig ist und wie wir Custom Controls ohne alte Xamarin Renderer bauen – mit Code für iOS und Android.