Skip to content

feat(motion): adopt spring tokens and add shared CSS-transition helper - #5112

Open
rjmreis wants to merge 5 commits into
callstack:mainfrom
rjmreis:css-transitions
Open

rjmreis wants to merge 5 commits into
callstack:mainfrom
rjmreis:css-transitions

Conversation

@rjmreis

@rjmreis rjmreis commented Sep 8, 2026 •

Copy link
Copy Markdown

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 getTransition helper.

  • getTransition() CSS-transition helper in theme/tokens/sys/motion.ts
  • Badge: uses helper, fixes duration/easing/reduce-motion
  • Checkbox: deduped onto helper, no behavior change
  • TextInput: label float/active-border now CSS transitions
  • SegmentedButtonItem: checkmark spring now fast.spatial token, matching Switch/FAB

Follow-up (separate issue): some #5015 files still use imperative withTiming for non-spring motion, out of scope here.

Related issue

Part of the MD3 motion modernization; no tracking issue yet.

Test plan

yarn typecheck/lint/jest pass (686 tests), incl. new getTransition + 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

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.
Copilot AI balanced review requested due to automatic review settings September 8, 2026 11:02

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 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(...) in src/theme/tokens/sys/motion.ts and unit tests validating token lookup + reduce-motion behavior.
  • Refactored TextInput label/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 Badge and Checkbox to use the shared transition helper; updated SegmentedButtonItem to use a theme spring token via toRawSpring(...).
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.

Comment thread src/components/TextInput/hooks.ts Outdated
Copilot AI review requested due to automatic review settings September 8, 2026 11:13

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 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

  • getLabelWrapper currently returns the first node with aria-hidden === true, but TextInput can render multiple aria-hidden elements (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 another aria-hidden node 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

Comment thread src/components/Badge.tsx
transitionDuration: 150 * scale,
transitionProperty: 'opacity',
...getTransition(theme, 'opacity', 'short3', 'standard', reduceMotion),
};

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have you considered typing this style as CSSStyle<ViewStyle> like in Checkbox.tsx?

@rjmreis rjmreis Sep 15, 2026 •

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call, done! Badge renders Animated.Text, so I've updated it to CSSStyle<TextStyle>.

Copilot AI review requested due to automatic review settings September 15, 2026 14:02

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 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 erases transitionProperty validation 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 default CSSTransitionProperties style type here so callers are constrained to supported style keys.
  • Files reviewed: 9/9 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

@rjmreis
rjmreis requested a review from JKobrynski September 23, 2026 07:06

@JKobrynski JKobrynski left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just one thing from me


const checkSpringConfig = React.useMemo(
() => ({
...toRawSpring(theme.motion.spring.slow.spatial),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
Copilot AI balanced review requested due to automatic review settings October 5, 2026 15:02

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

SegmentedButtonItem uses the fast spring token instead of the documented slow token, substantially changing its motion.

Review effort: Balanced
Findings: 1 Medium severity

Open (1)

Comment thread src/components/SegmentedButtons/SegmentedButtonItem.tsx
@github-actions

github-actions Bot commented Oct 6, 2026 •

Copy link
Copy Markdown

Found potential problems with the pull request:

  • The description is too long. Please keep it under 1000 characters.

@rjmreis
rjmreis requested a review from JKobrynski October 6, 2026 06:59

This branch has not been deployed

No deployments
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.

3 participants