How to announce page context ("Page X of Y") in HorizontalPager in Jetpack Compose with a11y?

3 weeks ago 32
ARTICLE AD BOX

I am building a custom selector component in Jetpack Compose. It consists of a HorizontalPager where each page contains a LazyVerticalGrid of selectable items (acting like Radio Buttons).

The problem: When a blind user navigates with TalkBack, the focus goes directly to the items inside the grid (e.g., "Square, Selected"). The user receives no context that they are inside a pager, nor do they know which page they are on (e.g., "Page 1 of 3").

Furthermore, simply swiping left/right navigates between grid items, making it difficult for the user to realize they can swipe with two fingers to change pages.

This is my current code:

Column(modifier = modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(24.dp)) { HorizontalPager( state = pagerState, modifier = Modifier.fillMaxWidth(), contentPadding = PaddingValues(horizontal = horizontalPadding), pageSpacing = horizontalPadding * 2, verticalAlignment = Alignment.Top ) { pageIndex -> val pageItems = pages[pageIndex] LazyVerticalGrid( userScrollEnabled = false, columns = GridCells.Fixed(maxColumns), verticalArrangement = Arrangement.spacedBy(spacingBetweenItems), horizontalArrangement = Arrangement.spacedBy(spacingBetweenItems), modifier = Modifier.fillMaxWidth() ) { items(pageItems) { item -> Box(modifier = Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) { Option( isSelected = selectedItem == item, iconRes = item.iconRes, label = stringResource(item.labelRes), onClick = { onItemClick(item) } ) } } } } // Pager indicator if (pages.size > 1) { Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy( 8.dp, alignment = Alignment.CenterHorizontally ) ) { repeat(pagerState.pageCount) { val color = if(pagerState.currentPage == it) Color.Blue else Color.Gray Box( modifier = Modifier .size(8.dp) .clip(CircleShape) .background(color) ) } } } }

Whay I have tried:

Invisible Semantics: I tried adding a Box with 0.dp size and heading() semantics with contentDescription="Page 1 of 3". TalkBack seems to prune this from the accessibility tree because it's invisible. Container Semantics: I tried adding semantics { stateDescription = "Page X of Y" } to the LazyVerticalGrid modifier. It doesn't consistently announce when entering the grid.
Read Entire Article