Apps · Entwicklung

Semantik in SwiftUI und Jetpack Compose

VoiceOver und TalkBack lesen nicht den Bildschirm, sondern eine unsichtbare Beschreibung dahinter: den Accessibility-Baum. In den deklarativen UI-Frameworks SwiftUI und Jetpack Compose entscheidest du über eine Handvoll Modifier, was darin steht: Name, Rolle, Wert und mögliche Aktionen jedes Elements. Diese Seite zeigt die wichtigsten Werkzeuge beider Frameworks und wann du Elemente zusammenfassen, trennen oder mit eigenen Aktionen ausstatten solltest.

  • 10 Minuten Lesezeit
  • Stand: Juni 2026

Was Semantik bedeutet

Eine Hilfstechnologie wie VoiceOver oder TalkBack sieht keine Pixel. Sie liest eine strukturierte Beschreibung deiner Oberfläche, den Accessibility-Baum(auch Semantik-Baum genannt). Jeder Knoten darin trägt die Informationen, die der Screenreader vorliest und bedienbar macht: einen Namen(Label), eine Rolle(Taste, Überschrift, Schalter), einen Wert(etwa „eingeschaltet") und eine Liste von Aktionen.

In SwiftUI und Jetpack Compose beschreibst du die Oberfläche deklarativ, und beide Frameworks erzeugen diesen Baum automatisch mit. Standard-Bausteine sind schon sinnvoll vorbelegt: Ein Button bekommt die Rolle „Taste", ein Text liefert seinen Inhalt als Namen. Semantik ist die Arbeit, die darüber hinausgeht: dort nachhelfen, wo das Framework nicht raten kann, etwa bei Icon-Buttons, eigenen Controls oder zusammengesetzten Karten.

Vom UI-Code zum Accessibility-Baum zum Screenreader Drei Stationen von links nach rechts: dein UI-Code, der daraus erzeugte Accessibility-Baum mit Name, Rolle und Wert je Knoten, und der Screenreader, der diesen Baum vorliest und bedienbar macht. UI-Code SwiftUI / Compose Accessibility-Baum Name: „Senden" Rolle: Taste Wert: keiner Aktion: aktivieren Screenreader VoiceOver / TalkBack
Dein Code erzeugt den Accessibility-Baum, der Screenreader liest ihn vor. Semantik-Modifier sind die Stellschrauben, mit denen du festlegst, was in den Knoten steht.

SwiftUI: die wichtigsten Modifier

In SwiftUI hängst du Semantik als Modifier an eine View. Die vier, mit denen du fast alles abdeckst: accessibilityLabel setzt den Namen, accessibilityValue den aktuellen Wert, accessibilityHint einen kurzen Hinweis auf die Wirkung, und accessibilityAddTraits ergänzt Rollen und Zustände wie .isButton , .isHeader oder .isSelected .

SwiftUI: Name, Rolle und Wert setzen

 // Icon-Button beschriften (sonst hört man nur „Taste") 
Button(action: send) {
    Image(systemName: "paperplane")
}
.accessibilityLabel("Nachricht senden") // Eine Textzeile als Überschrift markieren 
Text("Posteingang")
    .accessibilityAddTraits(.isHeader) // Wert eines eigenen Reglers ansagen 
StarRating(value: rating)
    .accessibilityLabel("Bewertung")
    .accessibilityValue("\(rating) von 5 Sternen")

Zwei Hinweise aus der Praxis: Formuliere das Label ohne die Rolle(„Senden", nicht „Senden-Taste"), die Rolle ergänzt VoiceOver selbst aus dem Trait. Und blende rein dekorative Elemente mit accessibilityHidden(true) aus, damit der Fokus nicht an bedeutungslosen Bildern hängen bleibt.

Jetpack Compose: das Semantics-System

Compose bündelt dieselben Informationen im Semantics-System. Jeder Composable kann über den Modifier Modifier.semantics { … } Eigenschaften setzen. Die wichtigsten: contentDescription für den Namen, role für die Rolle (etwa Role.Button ), stateDescription für den Zustand und heading() , um einen Knoten als Überschrift zu markieren. Bei einem Icon oder Image setzt du die contentDescription direkt als Parameter.

Jetpack Compose: contentDescription, Rolle und Überschrift

 // Icon-Button: contentDescription beschreibt die Funktion 
IconButton(onClick = ::send) {
    Icon(
        Icons.Filled.Send,
        contentDescription = "Nachricht senden"
    )
} // Eine Textzeile als Überschrift markieren 
Text(
    text = "Posteingang",
    modifier = Modifier.semantics { heading() }
) // Dekoratives Bild aus dem Baum nehmen 
Image(painter = bg, contentDescription = null)

Ein wichtiges Detail: Bei einem dekorativen Bild setzt du contentDescription = null (nicht den leeren String). Damit sagst du Compose ausdrücklich, dass dieses Element für Hilfstechnologien bedeutungslos ist, und es verschwindet aus dem Baum.

Wissens-Karte: gleiche Aufgabe, beide Frameworks
Aufgabe SwiftUI Jetpack Compose
Namen setzen accessibilityLabel contentDescription
Rolle ergänzen accessibilityAddTraits role = Role.…
Überschrift .isHeader -Trait heading()
Zustand / Wert accessibilityValue stateDescription
Element ausblenden accessibilityHidden(true) contentDescription = null bzw. clearAndSetSemantics

Zusammenfassen und Trennen

Eine Karte aus Bild, Titel und Untertitel besteht aus mehreren Elementen. Linear durch jedes einzelne zu wischen ist mühsam. Oft ist es besser, sie zu einem Element zusammenzufassen, das VoiceOver oder TalkBack in einem Rutsch vorliest.

In SwiftUI macht das accessibilityElement(children:) . Mit .combine werden die Beschriftungen der Kinder zu einem Element verschmolzen, mit .contain bleiben sie als Gruppe mit eigenen Kindern erhalten, mit .ignore werden sie verworfen, damit du das Element komplett selbst beschreibst. In Compose verschmilzt Modifier.semantics(mergeDescendants = true) die Kinder; Modifier.clearAndSetSemantics { … } wirft die Semantik der Kinder weg und ersetzt sie durch deine eigene.

Drei einzelne Stopps gegen ein zusammengefasstes Element Links eine Karte, bei der der Screenreader an Bild, Titel und Untertitel je einzeln anhält. Rechts dieselbe Karte als ein zusammengefasstes Element, das in einem Stopp vorgelesen wird. OHNE MERGE: DREI STOPPS Stopp 1 Stopp 2 Stopp 3 Fokus hält dreimal, Zusammenhang unklar. MIT MERGE: EIN STOPP Ein Stopp liest alles als zusammenhängende Einheit.
Ohne Zusammenfassen hält der Screenreader an jedem Teil der Karte einzeln. Mit .combine (SwiftUI) beziehungsweise mergeDescendants (Compose) wird sie zu einem Element, das in einem Stopp vorgelesen wird.

Eigene Aktionen statt versteckter Gesten

Manche Funktionen sind visuell an Gesten gebunden, etwa „nach links wischen zum Löschen" in einer Liste. Für Screenreader-Nutzer:innen ist so eine Geste unsichtbar. Die Lösung sind benannte eigene Aktionen: Du hängst dem Element eine Aktion mit klarem Namen an, die VoiceOver und TalkBack im Aktionen-Menü anbieten.

Eigene Aktion: SwiftUI und Compose

 // SwiftUI: benannte Aktion am Listeneintrag 
row.accessibilityAction(named: "Löschen") {
    delete(item)
} // Jetpack Compose: customActions in den Semantics 
Modifier.semantics {
    customActions = listOf(
        CustomAccessibilityAction("Löschen") { delete(item); true }
    )
}

So bleibt die schnelle Wisch-Geste für alle erhalten, und Screenreader-Nutzer:innen bekommen denselben Befehl über einen klar benannten Weg. Das ist fast immer besser, als die Geste nachzubauen.

Typische Fallstricke

Die immer gleichen Muster sorgen dafür, dass eine technisch saubere App für Screenreader trotzdem unbrauchbar wird, und sind fast immer schnell behoben:

Fallstricke und ihre Lösungen
Fallstrick Folge Lösung
Icon-Button ohne Label Ansage „Taste" ohne Funktion accessibilityLabel bzw. contentDescription
Rolle im Namen wiederholt Doppelte Ansage „Senden-Taste, Taste" Namen ohne Rolle formulieren, Rolle als Trait/Role setzen
Dekoratives Bild im Baum Fokus bleibt an bedeutungslosen Elementen hängen accessibilityHidden(true) bzw. contentDescription = null
Karte als viele Einzelstopps Mühsame Navigation, Zusammenhang unklar .combine bzw. mergeDescendants
Funktion nur per Wisch-Geste Für Screenreader unerreichbar benannte eigene Aktion ergänzen

Testen mit den Bordwerkzeugen

Den Accessibility-Baum kannst du dir direkt ansehen, ohne zu raten. Unter iOS zeigt der Accessibility Inspector aus Xcode pro Element Label, Wert, Traits und Aktionen. Unter Android prüfst du mit dem Layout Inspector den Semantik-Baum und mit Accessibility Scanner typische Fehler. Der ehrlichste Test bleibt aber, die App einmal selbst mit VoiceOver oder TalkBack zu bedienen.

Zwei verbreitete Irrtümer

Mythen-Check

Was über Semantik oft falsch erzählt wird

„Die Frameworks machen Barrierefreiheit automatisch." Sie machen viel automatisch, das stimmt: Standard-Komponenten sind sinnvoll vorbelegt. Aber genau dort, wo es individuell wird, bei Icon-Buttons, eigenen Controls, zusammengesetzten Karten und Gesten-Funktionen, kann das Framework nicht raten. Diese Lücken füllst nur du mit Semantik.

„Ein Hinweis ( accessibilityHint ) an jedem Element hilft." Zu viele Hinweise machen die Bedienung langsam und nervig. Ein Hinweis ist sinnvoll, wenn die Wirkung nicht offensichtlich ist. Name und Rolle dagegen sind die Pflicht, der Hinweis ist die Kür, sparsam eingesetzt.

Wie die Screenreader, für die du das alles baust, sich tatsächlich bedienen, zeigen die Artikel zu VoiceOver und TalkBack. Die offiziellen Quellen von Apple und Google sind unten verlinkt.