Kmp accessibility
Skill almasumdev/awesome-kotlin-multiplatform-agent-skills/.github/skills/ui/kmp-accessibility
Accessibility in Compose Multiplatform — semantics, focus order, contrast, dynamic type, and the places where Android TalkBack and iOS VoiceOver diverge. Use when building or auditing shared UI.From its SKILL.md
npx -y skills add almasumdev/awesome-kotlin-multiplatform-agent-skills --skill kmp-accessibilityAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
4.5 KB, ~1.0k tokens by cl100k_base, as published. Nobody here has run it
KMP Accessibility
Instructions
Compose Multiplatform maps Modifier.semantics to Android's AccessibilityNodeInfo and iOS's UIAccessibility. Most concepts transfer, but a few traits need platform-aware handling.
1. Core semantics
IconButton(
onClick = onFavorite,
modifier = Modifier.semantics {
contentDescription = if (isFavorite) "Remove from favorites" else "Add to favorites"
role = Role.Button
stateDescription = if (isFavorite) "Favorited" else "Not favorited"
},
) {
Icon(if (isFavorite) Icons.Filled.Favorite else Icons.Outlined.FavoriteBorder, contentDescription = null)
}
- Set
contentDescription = nullon decorative icons inside a labelled parent. - Use
stateDescriptionfor toggle state — both TalkBack and VoiceOver read it. - Prefer
Role.Button,Role.Switch,Role.Checkboxso the platform announces the control type.
2. Headings & grouping
Text(
"Account",
style = MaterialTheme.typography.headlineSmall,
modifier = Modifier.semantics { heading() },
)
Row(
modifier = Modifier.semantics(mergeDescendants = true) {
contentDescription = "Battery: 84 percent, charging"
},
) {
Icon(Icons.Filled.BatteryChargingFull, contentDescription = null)
Text("84%")
}
mergeDescendants = true is the KMP equivalent of iOS isAccessibilityElement = true — essential for composite controls.
3. Touch target size
Platform minimums differ: Android 48dp, iOS 44pt. Use the larger:
Modifier.minimumInteractiveComponentSize() // Material 3 helper, defaults to 48.dp
Wrap tap targets that look smaller than the hit box:
Box(
modifier = Modifier
.size(48.dp)
.clickable(onClickLabel = "Dismiss") { onDismiss() }
.padding(12.dp),
) { Icon(Icons.Filled.Close, contentDescription = null) }
4. Dynamic type & contrast
- Support user font scale: avoid
Modifier.size(...)on text containers; usewrapContentHeight. - Contrast: ensure
onSurface/onPrimarytoken pairs meet 4.5:1 for text. Material 3 defaults comply, but branded themes often don't — verify with tooling. - iOS has Bold Text and Increase Contrast system settings; these do not automatically propagate to Compose MP. Observe via
UIAccessibilityIsBoldTextEnabled()iniosMainand expose as aStateFlow<AccessibilityPrefs>.
5. Focus & traversal
val focusManager = LocalFocusManager.current
OutlinedTextField(
value = email, onValueChange = onEmailChange,
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
keyboardActions = KeyboardActions(onNext = { focusManager.moveFocus(FocusDirection.Down) }),
)
For custom traversal order, use Modifier.semantics { traversalIndex = 1f } — supported on both Android and iOS in CMP 1.7+.
6. Live regions & announcements
// Shared
val announcer = LocalAccessibilityAnnouncer.current // custom CompositionLocal
LaunchedEffect(message) { message?.let { announcer.announce(it) } }
Back it with platform implementations:
// androidMain
class AndroidAnnouncer(private val view: View) : AccessibilityAnnouncer {
override fun announce(text: String) = view.announceForAccessibility(text)
}
// iosMain
class IosAnnouncer : AccessibilityAnnouncer {
override fun announce(text: String) =
UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, text)
}
7. Testing
- Android:
accessibility-test-framework+ Espresso. - iOS:
XCUIElement.isAccessibilityElementassertions in XCTest, driven against the Compose view controller. - Shared: semantic assertions via
composeTestRule.onNodeWithContentDescription("…")work on every target.
Checklist
- Every interactive control has a
contentDescriptionor visible label merged into its semantics. - Decorative
Icons passcontentDescription = null. - Composite rows use
mergeDescendants = truewith a coherent description. - Tap targets are at least 48dp / 44pt.
- Dynamic type respected — no fixed-height text containers.
- Live region announcements go through a shared abstraction with Android/iOS
actuals. - Tested with TalkBack on Android and VoiceOver on iOS on a real device.
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.