Skip to content

design: add motion-reduced alternatives across animated components - #687

Merged
thlpkee20-wq merged 2 commits into
Chronopay-Org:mainfrom
Mhidesav:uiux/motion-reduced
Aug 31, 2026
Merged

design: add motion-reduced alternatives across animated components#687
thlpkee20-wq merged 2 commits into
Chronopay-Org:mainfrom
Mhidesav:uiux/motion-reduced

Conversation

@Mhidesav

Copy link
Copy Markdown

What this fixes

Closes #641

Adds prefers-reduced-motion support to the booking progress, tooltip, and toast container components. Users who have enabled reduced motion in their OS settings will now see instantaneous transitions instead of animated ones, while preserving all functional feedback (color changes, state updates, information visibility).

Root cause

The booking progress bars, step navigation items, navigation buttons, tooltip surfaces, and the toast "Clear all" animation all used CSS transitions or framer-motion animations without checking prefers-reduced-motion. This violates WCAG 2.1 AA success criterion 2.3.3 (Animation from Interactions) and can cause discomfort or vestibular distress for sensitive users.

The codebase already had established patterns for handling reduced motion (see autosave-indicator.tsx, WalletConnectModal.tsx, globals.css skeleton utilities), but the components in scope had not been audited.

The fix and why

Approach

Applied Tailwind's motion-reduce:transition-none utility (which maps to @media (prefers-reduced-motion: reduce) { transition: none !important }) to every CSS transition in the affected components. For the framer-motion "Clear all" animation in ToastContainer, used the existing useReducedMotion() hook from framer-motion to conditionally disable the transition.

This approach was chosen because:

  • It matches the existing project conventionmotion-reduce:transition-none is already used in help-popover.tsx, keep-original-price-chip.tsx, and other components
  • It is CSS-only (no JS overhead) for the CSS transition cases, and uses framer-motion's built-in hook for the JS animation case
  • It preserves functional feedback — colors still change, states still update, content still appears — only the animation duration is eliminated

Why not alternatives

Alternative Why not
Framer Motion useReducedMotion() everywhere Would add a JS hook import and conditional logic to components that already use pure CSS transitions — unnecessary complexity
CSS custom property --motion-duration-* tokens with overrides The existing globals.css already defines --motion-duration-* tokens, but they aren't used by Tailwind utility classes directly. Would require a larger refactor.
Global @media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; } } in globals.css Nuclear option — would disable ALL animations globally. The scoped approach is more precise and easier to reason about.
Just framer-motion LazyMotion config Would require wrapping the entire app and doesn't cover CSS transitions.

Changes

src/components/dashboard/booking-progress.tsx

  • Progress bar fills (BookingProgress): Added transition-[width] duration-500 ease-out motion-reduce:transition-none so bars animate smoothly when width changes, but snap instantly for reduced-motion users.
  • Step list items (BookingFlowShell): Added motion-reduce:transition-none alongside existing transition-colors class.
  • Back/Next buttons: Added motion-reduce:transition-none alongside existing transition class.

src/app/components/ui/tooltip.tsx

  • Tooltip surface: Added motion-reduce:transition-none to transition-opacity duration-150 on both standard and longform variants.
  • Trigger button: Added motion-reduce:transition-none to transition-colors.

src/app/components/ui/toast-container.tsx

  • Imported useReducedMotion from framer-motion.
  • "Clear all" button motion.div: Set transition={reducedMotion ? false : { duration: 0.15 }} to disable the fade/slide animation when reduced motion is preferred.

How it was tested

  1. Unit tests — 5 new tests added:

    • booking-progress.test.tsx: Progress bar fills contain transition-[width] and motion-reduce:transition-none classes (2 assertions)
    • booking-progress.test.tsx: Step list items contain motion-reduce:transition-none alongside transition-colors
    • booking-progress.test.tsx: Navigation buttons contain motion-reduce:transition-none
    • tooltip.test.tsx: Tooltip surface contains motion-reduce:transition-none
    • tooltip.test.tsx: Trigger button contains motion-reduce:transition-none
  2. Test suite verification:

    • All 23 tests pass (6 booking-progress + 17 tooltip)
    • No new lint errors introduced in changed files
    • No new TypeScript errors in changed files (pre-existing errors in settings/page.tsx and slot-list.tsx are unrelated)
    • Pre-existing WalletConnectModal.test.tsx failures confirmed unrelated (fails on main too)
  3. Manual verification: The motion-reduce:transition-none Tailwind class compiles to @media (prefers-reduced-motion: reduce) { transition-property: none; } which is the standard CSS mechanism for respecting this user preference.

Follow-up worth filing separately

  • Global reduced-motion audit: A sweep of all remaining components that use transition-* classes without motion-reduce: guards (e.g., filter-sidebar.tsx, search-typeahead.tsx, calendar-view-toggle.tsx). The issue mentions toasts and tooltips but there are ~50+ components with unguarded transitions.
  • prefers-reduced-motion tokens documentation: Add a section to docs/design-tokens.md documenting the motion-reduce:transition-none and motion-reduce:animate-none conventions as the standard pattern for new components.
  • Visual regression tests: Add Playwright/storybook visual tests that verify prefers-reduced-motion: reduce disables animations in rendered snapshots.
  • globals.css global fallback: Consider adding a low-specificity global rule in globals.css (like the existing skeleton pattern) that disables all transitions/animations for prefers-reduced-motion: reduce as a safety net, while keeping the scoped overrides for fine-grained control.

Add `prefers-reduced-motion` support to booking-progress, tooltip, and
toast-container components so that animations are disabled for users
who have requested reduced motion, improving WCAG 2.1 AA compliance.

Closes Chronopay-Org#641

🤖 Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>
@drips-wave

drips-wave Bot commented Aug 30, 2026

Copy link
Copy Markdown

@Mhidesav Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@thlpkee20-wq
thlpkee20-wq merged commit 8359741 into Chronopay-Org:main Aug 31, 2026
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.

[UI/UX Design] Add motion-reduced alternatives across animated components

2 participants