Skip to content

refactor(PaperProvider): add reduceMotion prop and useReduceMotion hook#4924

Merged
adrcotfas merged 1 commit into
v6from
@adrcotfas/refactor/accesibility
May 14, 2026
Merged

refactor(PaperProvider): add reduceMotion prop and useReduceMotion hook#4924
adrcotfas merged 1 commit into
v6from
@adrcotfas/refactor/accesibility

Conversation

@adrcotfas
Copy link
Copy Markdown
Collaborator

@adrcotfas adrcotfas commented May 6, 2026

Motivation

The library's existing reduce-motion support set animation.scale = 0 -- a v2-era flag that components had to explicitly check, with no connection to the MD3 theme.motion spring and duration tokens.

This PR wires OS accessibility into the theme properly. useAccessibleTheme replaces the inline PaperProvider logic, subscribes to reduceMotionChanged, and when active collapses theme.motion to instant springs and zero durations via the new reducedMotion preset. A clean theme.motion.prefersReducedMotion: boolean flag gives components a single readable signal instead of the magic scale === 0 check.

animation.scale is kept and still set to 0 for backward compatibility with components that haven't migrated yet. It is now formally deprecated in favour of theme.motion.prefersReducedMotion.

Two further deprecations are added while here: theme.mode ('adaptive' | 'exact'), which is an MD2 elevation-overlay concept superseded by tonal surface colors in theme.colors.elevation.*; and animation.defaultAnimationDuration, which was never read at runtime.
No component behavior changes. The reduce-motion path is functionally equivalent to before for all existing components. theme.motion.prefersReducedMotion and the reducedMotion preset are foundation plumbing -- they will be consumed when components migrate to theme.motion tokens in the per-component PRs that follow.

Related issue

See https://www.notion.so/callstack/React-Native-Paper-Foundation-for-MD3-Expressive-34c5d027c0f880edba3df107cd35946f?source=copy_link

Merge order:

Test plan

  • yarn typescript -- no new type errors
  • yarn test -- all tests pass

@callstack-bot
Copy link
Copy Markdown

callstack-bot commented May 6, 2026

Hey @adrcotfas, thank you for your pull request 🤗. The documentation from this branch can be viewed here.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 6, 2026

The mobile version of example app from this branch is ready! You can see it here.

Comment thread src/index.tsx Outdated
Comment thread src/theme/types/theme.ts Outdated
Comment thread src/index.tsx Outdated
Comment thread src/theme/tokens/sys/motion.ts Outdated
};
}

export function useAccessibleTheme(theme: Theme, enabled = true): Theme {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I don't think this should be wrapping theme in useTheme. This causes every component using this hook to subscribe to reduce motion.

If this is handled, then it should be done in PaperProvider in a single place so there's a single subscription.

But also, if prefersReducedMotion is in the theme, it conflicts with such an API if it's also handled internally. When the user passes a theme, which will already have prefersReducedMotion, is it also handled internally and overridden?

It probably makes sense to expose a separate prop reduceMotion="auto" or something like that on PaperProvider, then internally it can subscribe and expose the actual value in context. Then, on the component level, components can adapt their animations to reduce motion when necessary.

There is also the problem of the value not being known on initial render, as it's async, so if some component has animation on render, it won't properly respect this.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I refactored that part. Previously we had a bug because the reduce motion handling was listener only. The state stayed false forever until the user toggled reduce motion in the OS during the running session.
Now one frame is still wrong. Do we accept that for now or do you see a solution? Native modules might be an overkill for this edge case.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@adrcotfas i guess it's acceptable for now as it's rare to have motion animation fr initial render. there's also option to create native module but keep it optional, and fallback to react native's API if it's unavailable. i don't see another option.

@adrcotfas adrcotfas force-pushed the @adrcotfas/refactor/tokens_elevation branch 2 times, most recently from ae279cd to 94aa319 Compare May 11, 2026 14:45
@adrcotfas adrcotfas force-pushed the @adrcotfas/refactor/accesibility branch from b4a5696 to b4561ba Compare May 12, 2026 11:17
@adrcotfas adrcotfas force-pushed the @adrcotfas/refactor/tokens_elevation branch from 94aa319 to dbc5533 Compare May 12, 2026 12:25
@adrcotfas adrcotfas force-pushed the @adrcotfas/refactor/accesibility branch 2 times, most recently from 27f7121 to a9ec233 Compare May 12, 2026 13:11
@adrcotfas adrcotfas changed the title feat: add accessibility adaptation layer refactor(PaperProvider): add reduceMotion prop and useReduceMotion hook May 12, 2026
@adrcotfas adrcotfas requested a review from satya164 May 12, 2026 13:26
Comment thread src/core/PaperProvider.tsx Outdated
},
};
}, [colorScheme, props.theme, reduceMotionEnabled]);
} as Theme;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Avoid as as it's not typesafe. A return type annotation on the function should handle it.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

My bad for that - the cast was wrong but there was a pre-existing issue here so I reverted it.
A return annotation doesn't actually fix it though: props.theme is a deep-partial, so the shallow spread overwrites colors/fonts/motion/elevation with partials, and (): Theme => rejects the body for that reason.
Will fix properly (deep-merge per top-level key) in a follow-up.

@adrcotfas adrcotfas force-pushed the @adrcotfas/refactor/tokens_elevation branch from dbc5533 to 9c49c7c Compare May 14, 2026 05:35
Base automatically changed from @adrcotfas/refactor/tokens_elevation to v6 May 14, 2026 05:40
@adrcotfas adrcotfas force-pushed the @adrcotfas/refactor/accesibility branch from a9ec233 to ce6b6b7 Compare May 14, 2026 07:33
@adrcotfas adrcotfas requested a review from satya164 May 14, 2026 08:14
@adrcotfas adrcotfas added the v6 label May 14, 2026
@adrcotfas adrcotfas merged commit e7dff9f into v6 May 14, 2026
3 checks passed
@adrcotfas adrcotfas deleted the @adrcotfas/refactor/accesibility branch May 14, 2026 12:53
azizbecha pushed a commit to azizbecha/react-native-paper that referenced this pull request May 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants