| name | accessibility |
| description | Android accessibility for AI agents. Use this skill whenever implementing accessibility,
TalkBack support, content descriptions, semantic properties, touch target sizes, contrast
ratios, screen reader support, contentDescription, semantics modifier, clearAndSetSemantics,
mergeDescendants, heading, stateDescription, traversalOrder, custom actions, live regions,
accessibility service, WCAG compliance, accessibility audit, or any Compose accessibility.
Required for Play Store submission in many markets. Always apply before shipping any screen.
|
Android Accessibility
Accessibility is not optional — it's required by law in many markets and checked by the Play Store.
Rule 1: Content descriptions for all interactive elements
IconButton(onClick = onLike) {
Icon(
imageVector = if (isLiked) Icons.Filled.Favorite else Icons.Outlined.FavoriteBorder,
contentDescription = if (isLiked) "Remove from favorites" else "Add to favorites"
)
}
AsyncImage(
model = imageUrl,
contentDescription = "$productName product image"
)
Icon(Icons.Default.Star, contentDescription = null)
Icon(Icons.Default.Favorite, contentDescription = "icon")
Rule 2: Minimum touch target — 48×48dp
@Composable
fun SmallIconButton(onClick: () -> Unit, icon: ImageVector, contentDescription: String) {
Box(
modifier = Modifier
.size(48.dp)
.clickable(
onClick = onClick,
interactionSource = remember { MutableInteractionSource() },
indication = rememberRipple(bounded = false)
),
contentAlignment = Alignment.Center
) {
Icon(
imageVector = icon,
contentDescription = contentDescription,
modifier = Modifier.size(24.dp)
)
}
}
Icon(
imageVector = icon,
contentDescription = description,
modifier = Modifier.minimumInteractiveComponentSize()
)
Rule 3: Semantic properties for complex components
@Composable
fun ProductCard(product: Product, onAddToCart: () -> Unit) {
Card(
modifier = Modifier.semantics(mergeDescendants = true) {}
) {
Column {
Text(product.name)
Text("$${product.price}")
Text(if (product.inStock) "In stock" else "Out of stock")
Button(onClick = onAddToCart) { Text("Add to cart") }
}
}
}
Switch(
checked = isEnabled,
onCheckedChange = onToggle,
modifier = Modifier.semantics {
stateDescription = if (isEnabled) "Notifications on" else "Notifications off"
}
)
Text(
text = "Today's Orders",
style = MaterialTheme.typography.titleLarge,
modifier = Modifier.semantics { heading() }
)
LazyColumn {
items(items) { item ->
SwipeableItemCard(
item = item,
modifier = Modifier.semantics {
customActions = listOf(
CustomAccessibilityAction("Delete ${item.title}") {
onDelete(item.id)
true
},
CustomAccessibilityAction("Archive ${item.title}") {
onArchive(item.id)
true
}
)
}
)
}
}
Rule 4: Color contrast — WCAG AA minimum
Text(
text = "Important message",
color = MaterialTheme.colorScheme.onSurface
)
Text(text = "Label", color = Color(0xFF888888))
Rule 5: Screen reader traversal order
@Composable
fun ProductHeader(name: String, price: String, rating: Float) {
Row(modifier = Modifier.semantics { isTraversalGroup = true }) {
Column(modifier = Modifier.weight(1f)) {
Text(name, modifier = Modifier.semantics { traversalIndex = 0f })
Text(price, modifier = Modifier.semantics { traversalIndex = 1f })
}
RatingBar(rating, modifier = Modifier.semantics { traversalIndex = 2f })
}
}
Rule 6: Live regions for dynamic content
Text(
text = "Found ${results.size} results",
modifier = Modifier.semantics {
liveRegion = LiveRegionMode.Polite
}
)
Text(
text = errorMessage,
modifier = Modifier.semantics {
liveRegion = LiveRegionMode.Assertive
}
)
Accessibility checklist (run before every release)