Repository navigation
Conversation
Adds getTransition() helper, fixes Badge's reduce-motion/duration/easing, converts TextInput label float to CSS transitions, and moves SegmentedButtonItem's checkmark spring onto theme tokens.
There was a problem hiding this comment.
🔵 Needs a closer look
It changes core animation mechanics (tokenized CSS transitions + spring adoption) across widely used components like TextInput, which is cross-platform sensitive and should get final human verification.
Pull request overview
This PR continues the MD3 Expressive motion modernization by (1) introducing a shared helper for Reanimated CSS-transition styles derived from theme motion tokens, and (2) migrating/deduping several components to use those tokens and the CSS-transition tier consistently (including adopting a spring token for SegmentedButtonItem).
Changes:
- Added
getTransition(...)insrc/theme/tokens/sys/motion.tsand unit tests validating token lookup + reduce-motion behavior. - Refactored
TextInputlabel/indicator animations from imperative Reanimated timing/shared-values to render-driven CSS transitions, and added regression tests asserting the computed label styles across focus/blur. - Updated
BadgeandCheckboxto use the shared transition helper; updatedSegmentedButtonItemto use a theme spring token viatoRawSpring(...).
File summaries
| File | Description |
|---|---|
| src/theme/tokens/sys/motion.ts | Adds getTransition helper to standardize CSS transition duration/easing/property from motion tokens. |
| src/theme/tests/motion.test.ts | Adds unit coverage for getTransition token lookup, reduce-motion, and easing construction. |
| src/components/TextInput/TextInput.tsx | Removes now-unused animation handler types; consumes updated hook-driven animation state. |
| src/components/TextInput/hooks.ts | Replaces imperative label/indicator animation with boolean-state-driven CSS transition styles; wires reduce-motion via context. |
| src/components/TextInput/constants.ts | Removes timing/easing constants tied to imperative withTiming animation. |
| src/components/SegmentedButtons/SegmentedButtonItem.tsx | Replaces hardcoded spring config with theme spring token via toRawSpring. |
| src/components/Checkbox/Checkbox.tsx | Dedupes CSS transition props through getTransition. |
| src/components/Badge.tsx | Switches Badge opacity transition to tokenized getTransition + useReduceMotion. |
| src/components/tests/TextInput.test.tsx | Adds assertions for label wrapper/text styles across focus/blur in filled + outlined variants. |
Review details
- Files reviewed: 9/9 changed files
- Comments generated: 1
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
There was a problem hiding this comment.
🔵 Needs a closer look
It changes animation mechanisms and motion-token wiring in core components (notably TextInput), where subtle platform/runtime differences are hard to fully validate from diff review alone.
Review details
Suppressed comments (1)
Previously missed (1) — in code that hasn't changed since the last review.
src/components/tests/TextInput.test.tsx:83
getLabelWrappercurrently returns the first node witharia-hidden === true, butTextInputcan render multiplearia-hiddenelements (e.g. the error icon wrapper). That makes this helper non-specific and can cause future tests to assert styles on the wrong element if anotheraria-hiddennode appears earlier in the tree.
Consider narrowing the predicate to only match the label wrapper by additionally requiring a transition style marker (e.g. transitionProperty in the style object/array), which should be unique to the animated label wrapper in these tests.
- Files reviewed: 9/9 changed files
- Comments generated: 0 new
- Review effort level: Lite
| transitionDuration: 150 * scale, | ||
| transitionProperty: 'opacity', | ||
| ...getTransition(theme, 'opacity', 'short3', 'standard', reduceMotion), | ||
| }; |
There was a problem hiding this comment.
Have you considered typing this style as CSSStyle<ViewStyle> like in Checkbox.tsx?
There was a problem hiding this comment.
Good call, done! Badge renders Animated.Text, so I've updated it to CSSStyle<TextStyle>.
There was a problem hiding this comment.
🔵 Needs a closer look
The helper’s default generic currently permits invalid transition-property names.
Review details
Suppressed comments (1)
Previously missed (1) — in code that hasn't changed since the last review.
src/theme/tokens/sys/motion.ts:142
- The
Record<string, unknown>default erasestransitionPropertyvalidation because its keys accept any string, so typos such as'opactiy'compile and produce a no-op/invalid transition at runtime. Use Reanimated's own defaultCSSTransitionPropertiesstyle type here so callers are constrained to supported style keys.
- Files reviewed: 9/9 changed files
- Comments generated: 0 new
- Review effort level: Balanced
JKobrynski
left a comment
There was a problem hiding this comment.
Just one thing from me
|
|
||
| const checkSpringConfig = React.useMemo( | ||
| () => ({ | ||
| ...toRawSpring(theme.motion.spring.slow.spatial), |
There was a problem hiding this comment.
Should the checkmark spring be fast.spatial, like Switch and FAB? M3 maps fast to small components, slow to full-screen.
- SegmentedButtonItem checkmark spring now uses fast.spatial (small component) instead of slow.spatial, matching Switch/FAB precedent - getTransition's generic no longer erases transitionProperty validation; defaults to a real style union and Badge/Checkbox now specify their own concrete style type - TextInput.test.tsx's getLabelWrapper now also requires a transitionProperty marker (via getAnimatedStyle) so it can't match an unrelated aria-hidden node
|
Found potential problems with the pull request:
|

Motivation
MD3 Expressive replaces easing+duration with springs where motion is spring-shaped; CSS transitions are the default otherwise. #5015 handled the library swap + CSS-transition conversion; this PR adds spring adoption + a shared
getTransitionhelper.getTransition()CSS-transition helper intheme/tokens/sys/motion.tsBadge: uses helper, fixes duration/easing/reduce-motionCheckbox: deduped onto helper, no behavior changeTextInput: label float/active-border now CSS transitionsSegmentedButtonItem: checkmark spring nowfast.spatialtoken, matchingSwitch/FABFollow-up (separate issue): some #5015 files still use imperative
withTimingfor non-spring motion, out of scope here.Related issue
Part of the MD3 motion modernization; no tracking issue yet.
Test plan
yarn typecheck/lint/jestpass (686 tests), incl. newgetTransition+ TextInput focus/blur tests. Manual verification + screenshots below.Screenshots
Badge
Simulator.Screen.Recording.-.iPhone.17.Pro.-.2026-09-08.at.13.08.16.mov
Checkbox / Checkbox Item
Simulator.Screen.Recording.-.iPhone.17.Pro.-.2026-09-08.at.13.10.19.mov
TextInput
Simulator.Screen.Recording.-.iPhone.17.Pro.-.2026-09-08.at.12.56.43.mov
Segmented Buttons
Simulator.Screen.Recording.-.iPhone.17.Pro.-.2026-09-08.at.12.58.39.mov