Skip to content

feat: add ConnectedButtonGroup component#5028

Open
matkoson wants to merge 7 commits into
callstack:mainfrom
matkoson:feat/connected-button-group
Open

feat: add ConnectedButtonGroup component#5028
matkoson wants to merge 7 commits into
callstack:mainfrom
matkoson:feat/connected-button-group

Conversation

@matkoson

Copy link
Copy Markdown
Contributor

Motivation

The Material Design 3 spec deprecates segmented buttons in favor of the connected button group. This PR adds a ConnectedButtonGroup component implementing that spec as part of the ongoing modernization effort:

  • Single- and multi-select selection models (same buttons/value/onValueChange API shape as SegmentedButtons, easing migration).
  • Five sizes per the M3 button-group scale (extra-smallextra-large: 32/40/56/96/136dp) with per-size component tokens extracted into tokens.ts.
  • Animated shape morphing driven by the theme motion tokens: selected buttons round to a full pill, the connected inner corners sharpen while pressed, and the group's outer silhouette stays fully rounded. The animation honours the reduce-motion setting.
  • MD3 color roles: selected secondaryContainer/onSecondaryContainer, unselected surfaceContainer/onSurfaceVariant, disabled onSurface at 12% container / 38% content opacity (applied as styles so PlatformColor themes work).
  • Accessibility: buttons expose the radio (single-select, inside a radiogroup row) or checkbox (multi-select) role with checked/disabled state; per-button aria-label, optional selected check icon, hitSlop/background passthroughs.
  • Reuses the shared theme utilities (resolveCornerRadius/ShapeToken, toRawSpring, TouchableRipple's stateLayerPressed ripple default).
  • SegmentedButtons is marked @deprecated pointing to this component (non-breaking).
  • Wired into the example app, docs config, theme-colors table, and a docs screenshot.

Related issue

Fixes #4979

Test plan

  • yarn typecheck, yarn lint — clean.
  • yarn test ConnectedButtonGroup — 23 tests + 4 render snapshots covering selection (single/multi), sizing, MD3 color roles, disabled behavior, selection check, custom colors, accessibility roles, and shape tokens.
  • Example app → "Connected Button Group": verify tapping moves the selection pill (single-select), toggles accumulate (multi-select), sizes render at 32/40/56dp, and the disabled row is dimmed. Verified on an Android emulator (Pixel 9 Pro XL, API 36) and an iOS simulator (iPhone 16 Pro, iOS 18.3):
Android iOS
android ios

matkoson added 7 commits July 14, 2026 04:17
…group

Introduces ConnectedButtonGroup, the MD3 successor to SegmentedButtons.
Selected buttons morph to a fully-rounded shape, the connected inner corners
expand on press, and the group supports single- and multi-select across the
extra-small to extra-large size scale.

Component-specific tokens are extracted into tokens.ts; size, shape and color
resolution live in utils.ts. SegmentedButtons is marked as deprecated in
favour of the new component.
…ty overlay

Pre-blending the disabled container color with color().alpha() breaks on
Android dynamic themes where onSurface is a PlatformColor object rather
than a string — the fallback rendered a fully opaque near-black container.
Apply the MD3 12% disabled opacity as a style on an absolute-fill overlay
instead, which works with any ColorValue. Found during on-device Android
verification.
- tokens: pressed inner corner now morphs sharper than rest (M3
  ConnectedButtonGroup*Tokens: Inner=Small, PressedInner=ExtraSmall), and
  large/extra-large rest inner corners corrected to large(16)/largeIncreased(20).
- press morph: selected buttons also morph on press (pressed precedence over
  checked, matching Compose ToggleButton); pressOut returns to the rest radius.
- accessibility: buttons now use the radio (single-select) / checkbox
  (multi-select) role instead of button+aria-checked, valid on web and better
  announced natively.
- reduce-motion: shape morph honours the reduce-motion setting (ReduceMotion),
  matching Switch/FAB.
- docs: register ConnectedButtonGroup in component-docs.config so the
  deprecation link and generated page resolve.
- tests: cover shape tokens and role semantics (22 total).
- resolve corner shapes through the shared ShapeToken/resolveCornerRadius
  from theme/utils/shape instead of a local re-implementation.
- drop the per-button rippleColor prop and runtime ripple color derivation;
  TouchableRipple's stateLayerPressed default applies, consistent with the
  removal of customRippleColor across components.
- expose background and hitSlop passthroughs per button, mirroring
  SegmentedButtons, and remove the ineffective automatic touch-target
  expansion.
- drive the corner morph with the theme's fast spatial spring (toRawSpring),
  matching Switch and FAB, instead of timing curves.
- add radiogroup semantics to the single-select row and collision-proof
  list keys.
- honour intrinsic button widths in content-sized parents (flexBasis auto).
- export ConnectedButtonGroupSize, register a docs screenshot, keep the
  docs config alphabetical, and extend tests with render snapshots (23).
The docs build requires a committed page under 5.x/docs/components for
every entry in component-docs.config; add the ConnectedButtonGroup page
(usage, props, theme colors) mirroring SegmentedButtons, regenerate the
6.x content, and point the SegmentedButtons deprecation notice at the
correct relative path so the link resolves.
@matkoson
matkoson marked this pull request as ready for review July 16, 2026 10:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: add Connected Button Group

1 participant