Kmp accessibility
Skill almasumdev/awesome-kotlin-multiplatform-agent-skills/.github/skills/ui/kmp-accessibility
Curated agent skills, conventions, and workflows for building Kotlin Multiplatform (KMP) apps with AI coding agents.
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.
What its author says it does
Copied from the file, not written here
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.
SKILL.md
4.5 KB, 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.