Skip to content

feat: modernize RadioButton for MD3#5007

Open
burczu wants to merge 8 commits into
callstack:mainfrom
burczu:feat/radio-button-md3
Open

feat: modernize RadioButton for MD3#5007
burczu wants to merge 8 commits into
callstack:mainfrom
burczu:feat/radio-button-md3

Conversation

@burczu

@burczu burczu commented Jun 19, 2026

Copy link
Copy Markdown

Motivation

RadioButton still shipped as two platform-specific implementations (RadioButtonAndroid, RadioButtonIOS) with the iOS variant rendering a checkmark glyph rather than a Material radio. This is inconsistent with the rest of the v6 MD3 work (Checkbox, Switch, TextInput, FAB) and diverges from the M3 radio button spec.

This PR unifies RadioButton into a single MD3 component that mirrors the modernized Checkbox structure (single component, tokens.ts, error prop, accessible={false} inner control), without pulling in Checkbox's reanimated/focus-ring machinery (out of scope — radio ships with none today).

Related issue

Closes #4938

What changed

  • Unified component — merged RadioButtonAndroid + RadioButtonIOS into a single MD3 RadioButton (Android impl as base). Dropped RadioButton.Android, RadioButton.IOS, RadioButtonIOS, and the RadioButton.Item mode prop.
  • Tokens — new RadioButton/tokens.ts (MD3 dims 20/10/2 + color roles); colors resolved via tokens in utils.
  • error prop — added to RadioButton and RadioButton.Item (ring + dot use theme.colors.error; disabled/custom colors take precedence).
  • Single animation path (reanimated) — collapsed two RN Animated.Values into one shared value and migrated to react-native-reanimated (matching Checkbox): dot scales in with overshoot on selection, keyed on checked so it also animates inside a RadioButton.Group. Uses theme.motion MD3 duration/easing tokens and respects reduce-motion.
  • Disabled opacity — the control now dims to 38% (selectionControlOpacity was previously computed but never applied).
  • Group perf — memoized the provider value + stabilized onValueChange with use-latest-callback; consumers switched from Context.Consumer to useContext.
  • Item a11y — inner control is accessible={false} + container importantForAccessibility="no-hide-descendants", so a screen reader sees one radio per row; dropped per-radio accessibilityLiveRegion; labelMaxFontSizeMultiplier=1.5 + ${testID}-text for CheckboxItem parity.
  • Docs/examples — updated examples (Group/Item, leading/trailing, error checked+unchecked, disabled); removed the iOS-mode demo and the RadioButtonAndroid/RadioButtonIOS doc pages.

Breaking changes

  • Removed RadioButton.Android, RadioButton.IOS, RadioButtonIOS (and RadioButtonAndroidProps/RadioButtonIOSProps exports).
  • Removed the RadioButton.Item mode prop.
  • error prop added (no visual change for existing usage).

Test plan

  • yarn typescript, yarn lint, yarn test (snapshots updated) — green.
  • yarn --cwd docs build — green.
  • Manually verified on iOS sim, Android emulator, and web: single selection, dot scale-in (standalone and in a Group), error (checked/unchecked), disabled dimming, leading/trailing, ripple, single a11y node per Item row.

Videos

radio_ios.mp4
radio_android.mp4
radio_web.mp4

@burczu burczu marked this pull request as ready for review June 19, 2026 12:07
@burczu burczu requested a review from satya164 June 19, 2026 12:15
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.

refactor(radio-button): modernize selection model and improve MD3 compliance

1 participant