Skip to content

feat(a11y): respect prefers-reduced-motion — wire MotionConfig into BladeProvider and add useReducedMotion hook#3368

Open
starboyvarun wants to merge 1 commit intorazorpay:masterfrom
starboyvarun:feat/prefers-reduced-motion-support
Open

feat(a11y): respect prefers-reduced-motion — wire MotionConfig into BladeProvider and add useReducedMotion hook#3368
starboyvarun wants to merge 1 commit intorazorpay:masterfrom
starboyvarun:feat/prefers-reduced-motion-support

Conversation

@starboyvarun
Copy link
Copy Markdown

Problem

Blade had zero support for prefers-reduced-motion. Users with vestibular disorders, epilepsy, or general motion sensitivity can set "Reduce Motion" in their OS — macOS, iOS, Windows, and Android all expose this setting. When set, the browser emits prefers-reduced-motion: reduce, and applications are expected to minimize or eliminate non-essential animation.

Every Blade animation — entrance/exit transitions, scale effects, stagger sequences, interactive hover animations — played at full speed regardless of this OS preference. This is a WCAG 2.1 SC 2.3.3 (Animation from Interactions) violation and affects real users.

A search across the entire codebase returned exactly one result for prefers-reduced-motion — a JSDoc comment in BaseMotion.tsx that claimed the component "handles reduced motion" but contained no implementation.

Solution

Part 1 — BladeProvider.web.tsx

framer-motion ships a <MotionConfig reducedMotion="user"> provider that reads the OS preference and sets all animation durations/delays to 0 for every framer-motion component inside it. Adding this to BladeProvider — where all other system-wide providers already live — covers every component that uses BaseMotion, BaseMotionEntryExit, Scale, Stagger, AnimateInteractions, or any other framer-motion-powered animation. Zero changes to individual components required.

// BladeProvider.web.tsx — the entire fix for framer-motion animations
<MotionConfig reducedMotion="user">
  <DrawerStackProvider>
    <BottomSheetStackProvider>{children}</BottomSheetStackProvider>
  </DrawerStackProvider>
</MotionConfig>

reducedMotion="user" is dynamic — if the user toggles the OS setting while the app is running, framer-motion reacts without a page reload.

Part 2 — useReducedMotion hook

Not all Blade animations use framer-motion. CSS transition and animation properties in styled-components (Toast entrance/exit, Modal backdrop fade, etc.) are not controlled by MotionConfig. Blade consumers building custom components on top of Blade also need a way to synchronize with the OS preference.

The new useReducedMotion() hook reads window.matchMedia('(prefers-reduced-motion: reduce)') directly, subscribes to OS-level changes, and returns a stable boolean. Exported from ~utils as part of the public API.

import { useReducedMotion } from '@razorpay/blade/utils';

const prefersReducedMotion = useReducedMotion();

<Box
  transitionDuration={prefersReducedMotion ? 'duration.2xquick' : 'duration.moderate'}
/>

Files Changed

  • packages/blade/src/components/BladeProvider/BladeProvider.web.tsx — import MotionConfig, wrap children
  • packages/blade/src/utils/useReducedMotion.ts — new hook with JSDoc and OS change subscription
  • packages/blade/src/utils/index.ts — export useReducedMotion from public API
  • .changeset/motion-honors-user.mdminor bump (new exported hook)

Testing

To verify MotionConfig coverage:

  1. macOS: System Settings → Accessibility → Display → Reduce Motion ✓
  2. iOS: Settings → Accessibility → Motion → Reduce Motion ✓
  3. Windows: Settings → Ease of Access → Display → Show animations ✓
  4. Or use Chrome DevTools: Rendering → Emulate CSS media → prefers-reduced-motion: reduce

With the setting on, all BaseMotion, Scale, Stagger, and AnimateInteractions animations should complete instantly (no visible transition). With it off, animations play normally.

To verify useReducedMotion:

const prefersReducedMotion = useReducedMotion();
console.log(prefersReducedMotion); // true when OS reduce motion is on

Toggle the OS setting — the value updates without a page reload.

Scope Note

CSS-based animations in styled-components (Toast entrance, certain Modal transitions) are outside the scope of MotionConfig. Those can be addressed in a follow-up using useReducedMotion within the relevant styled-components, following the same pattern established here.

…ations

Users with vestibular disorders or motion sensitivity can set "Reduce Motion"
in their OS settings, which emits prefers-reduced-motion: reduce. Blade had
zero support for this — every animation played at full speed regardless of
this preference.

Two changes:
1. BladeProvider.web.tsx: wrap children in <MotionConfig reducedMotion="user">.
   framer-motion reads the OS preference and sets all animation durations to 0
   when reduce motion is enabled. This automatically covers every component that
   uses BaseMotion, Scale, Stagger, AnimateInteractions, or any framer-motion
   powered animation — with no changes to individual components.

2. useReducedMotion hook: a new utility hook that reads the same media query
   for CSS-based transitions (styled-components animations, canvas, etc.) and
   reacts to OS-level changes without a page reload. Exported from ~utils so
   Blade consumers can sync their own animations with the system preference.
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 3, 2026

🦋 Changeset detected

Latest commit: f8f222b

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@razorpay/blade Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 3, 2026

✅ PR title follows Conventional Commits specification.

@codesandbox-ci
Copy link
Copy Markdown

codesandbox-ci Bot commented May 3, 2026

This pull request is automatically built and testable in CodeSandbox.

To see build info of the built libraries, click here or the icon next to each commit SHA.

Latest deployment of this branch, based on commit f8f222b:

Sandbox Source
razorpay/blade: basic Configuration

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.

1 participant