Skip to content

refactor(android): replace pill chips with M3 scrollable tabs in inserter#512

Open
jkmassel wants to merge 2 commits into
trunkfrom
jkmassel/block-inserter-chip-touch-targets
Open

refactor(android): replace pill chips with M3 scrollable tabs in inserter#512
jkmassel wants to merge 2 commits into
trunkfrom
jkmassel/block-inserter-chip-touch-targets

Conversation

@jkmassel
Copy link
Copy Markdown
Contributor

@jkmassel jkmassel commented May 13, 2026

Summary

Two related changes to the Android block inserter, all introduced by #461:

  1. Rebuild the category strip on SecondaryScrollableTabRow + Tab. Replaces the hand-rolled scrollable pill-chip row. The platform component provides selectable-group semantics, horizontal scroll, auto-scroll-to-selected, ripple, Role.Tab a11y, the underline indicator + divider, and the 48dp touch target — all for free. Custom edgePadding aligns the first tab label's leading edge with the search bar's outer left edge.

  2. Targeted TalkBack semantics fixes elsewhere in the sheet — header heading(), search-field contentDescription with a cleared placeholder, block-tile Role.Button, empty-state live region with a stable announcement.

Independent of #509 / #510 / #511.

Light Dark
Inserter in light mode showing the new scrollable secondary tab row Inserter in dark mode showing the same tab row blending with the dark sheet surface
Recent tab selected; primary-color underline indicator; first label aligned with search-bar edge Same layout against the dark bottom-sheet surface

Fix

1. M3 scrollable tabs

SecondaryScrollableTabRow(
    selectedTabIndex = selectedIndex,
    containerColor = ComposeColor.Transparent,
    edgePadding = (SEARCH_HORIZONTAL_PAD_DP - TAB_INTERNAL_TEXT_PAD_DP).dp,
    modifier = Modifier.fillMaxWidth(),
) {
    tabs.forEach { tab ->
        Tab(
            selected = tab == selected,
            onClick = { onSelect(tab) },
            text = { Text(stringResource(tab.labelRes)) },
        )
    }
}

Deletes the custom CategoryChip, its shared MutableInteractionSource ripple-clip trick, and all the chip-design constants (CHIP_*, TOUCH_TARGET_MIN_DP, TABS_*_PAD_*). edgePadding = 4.dp (computed as SEARCH_HORIZONTAL_PAD_DP - TAB_INTERNAL_TEXT_PAD_DP) lines the first tab label's leading edge up with the search bar's outer left edge. containerColor = Transparent so the tab row blends with the bottom-sheet surface rather than rendering its own opaque container.

M3 version note: M3 1.3.1 (pinned by Compose BOM 2024.12.01) hardcodes ScrollableTabRowMinimumTabWidth = 90.dp. The minTabWidth parameter that would let us override it ships in 1.4.0 and isn't worth a project-wide BOM bump for this PR — short labels like "Text" and "Media" render content-sized inside a 90dp slot.

2. Other TalkBack semantics

  • Header title gets heading() so TalkBack users can navigate by heading.
  • Search field (BasicTextField) gets a contentDescription matching its visible placeholder, and the placeholder Text uses clearAndSetSemantics {} so it doesn't double-announce alongside the field. TalkBack reads "Search blocks, edit box" empty; "Search blocks, edit box, " filled.
  • Block tiles get role = Role.Button on their clickable so TalkBack announces them as buttons.
  • Empty state ("no results") gets liveRegion = Polite so TalkBack announces the state change when search filters the grid down to zero. The visible Text still shows "No blocks match ''" for sighted users, but the live region's contentDescription is the constant "No results" and the inner Text uses clearAndSetSemantics {}. TalkBack announces once on the empty-results transition rather than re-announcing on every keystroke. Blind users can recover the query by focusing the search field, which reads back editableText.

Touch-target audit also covered the close button and search-clear IconButton — both already meet 48dp via M3's minimumInteractiveComponentSize, which expands the layout-node bounds outside the user's Modifier.size.

Test plan

  • Open the inserter on a device. Category strip looks like a standard M3 secondary scrollable tab row — labels with an underline indicator under the selected tab, transparent container.
  • Tapping any tab animates the underline indicator smoothly to the new tab.
  • Opening the inserter with a selected category that's offscreen-right auto-scrolls the strip to reveal it.
  • The first tab label's leading edge lines up vertically with the search bar's outer left edge.
  • TalkBack announces each tab as ", tab, selected" or ", tab, not selected".
  • TalkBack announces the search field as "Search blocks, edit box" when empty and "Search blocks, edit box, " when filled — only once per focus, no duplicated label from the placeholder.
  • TalkBack announces block tiles as buttons.
  • TalkBack reads the header title as a heading and lets the user navigate by heading.
  • TalkBack announces "No results" exactly once when search filters the grid to zero, and does not re-announce on every keystroke while results remain empty.
  • ./gradlew :Gutenberg:detekt :Gutenberg:assembleDebug :Gutenberg:testDebugUnitTest passes.

@github-actions github-actions Bot added the [Type] Bug An existing feature does not function as intended label May 13, 2026
@wpmobilebot
Copy link
Copy Markdown

wpmobilebot commented May 13, 2026

XCFramework Build

This PR's XCFramework is available for testing. Add the following to your Package.swift:

.package(url: "https://github.com/wordpress-mobile/GutenbergKit", branch: "pr-build/512")

Built from 533e5e2

@jkmassel jkmassel changed the title fix(android): meet 48dp touch target on block inserter category chips fix(android): a11y improvements to block inserter May 13, 2026
@jkmassel jkmassel force-pushed the jkmassel/block-inserter-chip-touch-targets branch 4 times, most recently from f12d97b to 0d42b4e Compare May 13, 2026 19:15
@jkmassel jkmassel changed the title fix(android): a11y improvements to block inserter refactor(android): replace pill chips with M3 scrollable tabs in inserter May 13, 2026
…rter

Rebuilds the category strip on `SecondaryScrollableTabRow` + `Tab`. The
platform component provides selectable-group semantics, horizontal
scroll, auto-scroll-to-selected, ripple, `Role.Tab` a11y, the underline
indicator + divider, and a 48dp touch target — all for free. Deletes
the custom `CategoryChip`, its shared `MutableInteractionSource` ripple-
clip trick, and the chip-design constants. `edgePadding` is set to
`SEARCH_HORIZONTAL_PAD_DP - TAB_INTERNAL_TEXT_PAD_DP` so the first tab
label's leading edge lines up with the search bar's outer left edge.

M3 1.3.1 hardcodes `ScrollableTabRowMinimumTabWidth = 90.dp`. The
`minTabWidth` parameter that would let us override it ships in 1.4.0
and isn't worth a project-wide Compose BOM bump for this PR.

Also folds in a few targeted TalkBack semantics fixes elsewhere in the
sheet, all introduced by #461:

- Header title gets `heading()` for heading navigation.
- Search field's `BasicTextField` gets a `contentDescription` matching
  its visible placeholder; the placeholder Text uses `clearAndSetSemantics {}`
  so it doesn't double-announce alongside the field.
- Block tiles get `role = Role.Button` on their `clickable`.
- Empty state gets `liveRegion = Polite` with a constant `contentDescription`
  ("No results") and `clearAndSetSemantics {}` on the visible Text, so
  TalkBack announces once on the empty-results transition rather than
  re-announcing on every keystroke. Blind users recover the query by
  focusing the search field (TalkBack reads back `editableText`).
@jkmassel jkmassel force-pushed the jkmassel/block-inserter-chip-touch-targets branch from 0d42b4e to ec3e135 Compare May 13, 2026 21:53
The button was using `title={ __( 'Add block' ) }` on the
`@wordpress/components` `<Button>`, which only sets the HTML `title`
attribute (a hover tooltip on desktop). For an icon-only button,
TalkBack on Android either skips the node or announces something
unhelpful — the canonical accessible name comes from `aria-label`,
which `@wordpress/components` sets via the `label` prop, not `title`.

Switch to `label={ __( 'Add block' ) }` so the rendered HTML carries
`aria-label="Add block"` and TalkBack announces "Add block, button"
when focused. Empirically verified on a Pixel 9 Pro XL.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[Type] Bug An existing feature does not function as intended

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants