Skip to content

fix(remix): honor reduced motion in animated components - #327

Open
leoafarias wants to merge 2 commits into
mainfrom
fix/remix-reduced-motion
Open

leoafarias wants to merge 2 commits into
mainfrom
fix/remix-reduced-motion

Conversation

@leoafarias

@leoafarias leoafarias commented Oct 27, 2026 •

Copy link
Copy Markdown
Collaborator

Description

Remix components that hard-code their own motion ignored reduced motion, and MediaQuery.disableAnimations alone misses iOS Reduce Motion (iOS only sets the platform accessibility reduceMotion).

  • Add RemixReducedMotionBuilder. Its builder receives whether the user asked for reduced motion (MediaQuery.disableAnimations or the platform reduceMotion) and rebuilds only when that value changes. A MediaQuery override can force reduced motion on but cannot turn the platform reduceMotion off. Same definition as naked_ui's reduced-motion read in fix(naked_ui): honor reduced motion in dialogs, tooltips, disclosures, and toasts #330, so both packages agree.
  • Select menu and accordion panel open and close without a transition; RemixSpinner stops on a static frame. Widget tree and semantics are the same in both modes.
  • RemixSkeleton and RemixSidebar, which already followed MediaQuery.disableAnimations, now also follow iOS Reduce Motion; a platform-only change settles a sidebar collapse already under way. RemixSkeleton no longer throws without a MediaQuery ancestor.
  • Which components follow which signal with this PR: select, accordion, spinner, skeleton, and sidebar follow both. Dialogs, tooltips, disclosure, and toast are Naked UI components and change in fix(naked_ui): honor reduced motion in dialogs, tooltips, disclosures, and toasts #330, not here.
  • Reviewable decision (D8): honor iOS Reduce Motion through one Remix helper rather than documenting Android/desktop only. Skeleton and sidebar behavior change because of it.

Landing order: land after #330 (naked_ui reduced motion); the remix release PR raises the naked_ui floor to the release that contains it. There is no code dependency, and the docs here do not promise dialog or tooltip behavior.

Conflict note: #323 (fix/remix-sidebar-media-query) changes the same lines in sidebar_widget.dart; resolve by keeping this PR's version (observer plus the internal reduced-motion read).

Known gap, follow-up upstream in Mix: .animate(...) styles, including preset recipes, are not covered. Mix never reads reduced motion and StyleBuilder offers no interception point. A zero-duration curve asserts (TweenSequenceItem weight > 0, though 1 ms is accepted); StyleSpec.copyWith cannot clear animation, though a fresh StyleSpec can; MixInteractionDetector and _ExternalControllerProvider are not exported. Flutter already shortens Mix curve animations under the platform disableAnimations; springs, iOS Reduce Motion, and MediaQuery overrides still animate. The guide shows gating .animate with RemixReducedMotionBuilder. The proper fix is in Mix's StyleAnimationBuilder (btwld/mix#1070 was closed unmerged).

Validation

  • New behavior tests for the builder (default, media query, platform reduceMotion, live change both ways, no rebuild on unrelated accessibility changes, the one-way MediaQuery override, observer removal), select, accordion, and spinner, plus sidebar (including a platform-only flip mid-collapse) and skeleton (platform cases, no MediaQuery ancestor). 17 tests fail on the unchanged components; the rebuild-on-change, sidebar mid-transition, and skeleton tests fail with their fix reverted.
  • flutter test in packages/remix (2864) and registry_source (1390), dart analyze, root dart format, melos run ci:checks, melos run docs:check.

Related Issues

btwld/mix#1070 (closed) for the Mix gap. Supersedes the closed #258 for the Remix components.


Checklist

  • My PR includes unit or integration tests for all changed/updated/fixed behaviors.
  • I have updated or added relevant documentation (doc comments with ///).
  • I am prepared to follow up on review comments in a timely manner.

Breaking Change

  • Yes, this is a breaking change.
  • No, this is not a breaking change.

Add RemixReducedMotion, which reports whether MediaQuery.disableAnimations
or the platform's accessibility reduceMotion is set and rebuilds when either
changes. iOS Reduce Motion only sets reduceMotion, so reading
MediaQuery.disableAnimations alone missed it.

The select menu and accordion panel now open and close without a
transition, and RemixSpinner holds a static frame. Skeleton and sidebar,
which already followed MediaQuery.disableAnimations, now follow iOS Reduce
Motion too. Style animations declared with .animate(...) are driven by Mix
and are not affected.
Rename RemixReducedMotion to RemixReducedMotionBuilder and drop its static
of. The select reads the preference through an internal function when a
transition starts, and the sidebar observes accessibility changes so a
platform-only change settles a collapse already under way. The builder only
rebuilds when the computed preference changes.

Docs state which components follow which signal, no longer promise dialog
or tooltip behavior that comes from Naked UI, and describe the Mix gap.
RemixSkeleton no longer throws without a MediaQuery ancestor.
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 27, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-27T01:27:48.365260Z 2c59c79 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2c59c79fb4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +304 to +306
duration: reduceMotion
? Duration.zero
: const Duration(milliseconds: 200),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Restore exit animations when reduced motion is turned off

If a panel is expanded with reduced motion enabled, then the preference is turned off while the panel remains expanded, its next collapse still happens instantly. AnimatedSwitcher captures the controller duration when each child enters, so changing the widget's duration back to 200 ms does not update the existing panel's zero-duration controller. The restoration test changes the preference before expanding and therefore misses this case. Ensure existing panels regain their exit animation when reduced motion is disabled, without remounting their state.

Useful? React with 👍 / 👎.

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.

1 participant