Skip to content

fix: respect prefers-reduced-motion in GlowingEffect component - #534

Open
Somil450 wants to merge 1 commit into
piyushdotcomm:mainfrom
Somil450:fix/524-respect-prefers-reduced-motion
Open

fix: respect prefers-reduced-motion in GlowingEffect component#534
Somil450 wants to merge 1 commit into
piyushdotcomm:mainfrom
Somil450:fix/524-respect-prefers-reduced-motion

Conversation

@Somil450

Copy link
Copy Markdown

Summary

Closes #524
Adds useReducedMotion() from motion/react to the GlowingEffect component to stop its JS-driven animation when the user has enabled the OS-level "Reduce motion" preference.

Problem

The CSS media query already in app/globals.css covers CSS-based animations. However, GlowingEffect uses Motion's animate() JavaScript API to drive a conic-gradient rotation - a JS animation that completely bypasses CSS media queries. Users who set "Reduce motion" in their OS still had this animation running.

Change

// components/ui/glowing-effect.tsx

-import { animate } from "motion/react";
+import { animate, useReducedMotion } from "motion/react";

 // inside the component:
+const prefersReducedMotion = useReducedMotion();

 useEffect(() => {
-  if (disabled || !isVisible) return;
- +  if (disabled || !isVisible || prefersReducedMotion) return;
-    // ... event listeners
-  }, [handleMove, disabled, isVisible, prefersReducedMotion]);
- ```
## Why this matters

- The CSS-only fix (`animation-duration: 0.01ms`) cannot stop JS-driven animations
- - `useReducedMotion()` reads `window.matchMedia('(prefers-reduced-motion: reduce)')` reactively - it updates automatically if the user changes the OS setting at runtime
- - Zero effect on users who have not enabled reduced motion
## Files Changed

- `components/ui/glowing-effect.tsx` - 3 lines changed

Closes piyushdotcomm#524

The CSS media query in globals.css already halts CSS animations for
users with prefers-reduced-motion. However the GlowingEffect component
uses Motion's animate() JS API for its conic-gradient rotation, which
bypasses CSS media queries entirely.

Adds useReducedMotion() from motion/react (2 lines) to bail out of the
pointermove/scroll effect handlers when the user prefers reduced motion,
preventing the JS-driven animation from running at all.
@Somil450
Somil450 requested a review from piyushdotcomm as a code owner July 31, 2026 09:06
@github-actions

Copy link
Copy Markdown

👋 Thanks for opening a PR, @Somil450!

Your PR has entered the 🚦 PR Review Pipeline.

Standard PR detected — your PR will follow the standard review pipeline.


What happens next

Stage Reviewer Checks
Stage 1 — Automated Validation 🤖 Bot DCO · Format · AI/Slop · Duplicate
Stage 2 — Human Review 👥 Maintainer Code + Quality Review
Stage 3 — PA / Maintainer Review 🔑 Project Admin Final Merge Decision

A pipeline status comment will appear below and update automatically as your PR progresses.


While you wait

  • Sign all commits (git commit -s)
  • Link your issue (Closes #123)
  • Use a feature branch (not main)
  • Avoid unrelated changes

This comment is posted only once.

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@Somil450, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 35 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: cd468bb9-fd61-484b-8d2d-7614554dd896

📥 Commits

Reviewing files that changed from the base of the PR and between 4ffe26f and 4c64684.

📒 Files selected for processing (1)
  • components/ui/glowing-effect.tsx

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added the bug Something isn't working label Jul 31, 2026
@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Fix: respect prefers-reduced-motion in GlowingEffect

🐞 Bug fix 🕐 10-20 Minutes

Grey Divider

AI Description

• Add Motion's reduced-motion hook to detect OS “Reduce motion” preference.
• Skip GlowingEffect’s JS-driven gradient rotation when reduced motion is enabled.
• Prevent pointer/scroll listeners and animation work for reduced-motion users.
Diagram

graph TD
  A["GlowingEffect"] --> B["useEffect (setup)"] --> C{"Reduced motion?"}
  X{{"OS: prefers-reduced-motion"}} --> Y["useReducedMotion()"] --> C
  C -- "yes" --> D["Bail out (no JS animation)"]
  C -- "no" --> E["Attach listeners"] --> F["animate() conic gradient"]

  subgraph Legend
    direction LR
    _cmp["Component"] ~~~ _dec{"Decision"} ~~~ _ext{{"External setting"}}
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Global Motion reduced-motion configuration
  • ➕ Ensures consistent reduced-motion behavior across all Motion animations
  • ➕ Avoids per-component gating logic if many components animate
  • ➖ Requires broader wiring (e.g., provider/config) and may have wider behavioral impact
  • ➖ Still may need exceptions for specific components
2. Replace JS rotation with CSS animation
  • ➕ Automatically respects prefers-reduced-motion CSS media queries
  • ➕ Potentially simpler runtime behavior (no JS listeners/RAF)
  • ➖ May not achieve the same dynamic behavior as Motion’s JS animate() approach
  • ➖ Could require rework of the existing interaction model

Recommendation: The PR’s approach (component-level useReducedMotion() gate) is the best minimal, low-risk fix for this specific accessibility bug. If the codebase contains multiple Motion-driven JS animations, consider additionally adding a global Motion reduced-motion configuration to enforce consistent behavior everywhere.

Files changed (1) +3 / -2

Bug fix (1) +3 / -2
glowing-effect.tsxGate JS animation on prefers-reduced-motion +3/-2

Gate JS animation on prefers-reduced-motion

• Imports Motion’s useReducedMotion() and reads the OS reduced-motion preference in the component. Updates the effect guard to early-return when reduced motion is enabled, preventing event listener setup and JS-driven gradient rotation.

components/ui/glowing-effect.tsx

@qodo-code-review

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (0) 📜 Skill insights (0)

Context used
✅ Compliance rules (platform): 22 rules

Grey Divider


Action required

1. Missing effect dependency 🐞 Bug ≡ Correctness
Description
GlowingEffect’s event-listener useEffect reads prefersReducedMotion but omits it from the
dependency array, so changes to the reduced-motion preference won’t re-run the effect and may leave
listeners (and associated animation work) active until some other dependency changes or the
component unmounts. This is also the pattern flagged by the React hooks exhaustive-deps rule
typically enabled via next/core-web-vitals.
Code

components/ui/glowing-effect.tsx[118]

+        if (disabled || !isVisible || prefersReducedMotion) return;
Relevance

●●● Strong

Team has accepted prior useEffect lifecycle/deps fixes to prevent stale behavior and ensure proper
cleanup/reruns.

PR-#219
PR-#190

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The effect uses prefersReducedMotion in its guard but the dependency array does not include it, so
React won’t re-run/cleanup this effect when only the reduced-motion preference changes. The repo’s
ESLint config extends next/core-web-vitals, which typically enables hooks dependency checking for
this exact situation.

components/ui/glowing-effect.tsx[36-36]
components/ui/glowing-effect.tsx[117-135]
eslint.config.mjs[12-14]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`prefersReducedMotion` is used inside the event-listener `useEffect` but is not included in its dependency array. As a result, toggling the OS-level reduced-motion preference while the component is mounted will not trigger effect cleanup/re-subscription.

### Issue Context
- `prefersReducedMotion` is read via `useReducedMotion()`.
- The effect condition now early-returns when `prefersReducedMotion` is true.
- The effect dependency list currently does not include `prefersReducedMotion`.

### Fix Focus Areas
- components/ui/glowing-effect.tsx[117-135]

### Proposed fix
Update the dependency array to include `prefersReducedMotion` so the effect cleans up listeners when the preference changes:

```ts
useEffect(() => {
 if (disabled || !isVisible || prefersReducedMotion) return;
 // ...
}, [handleMove, disabled, isVisible, prefersReducedMotion]);
```

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

To customize comments, go to the Qodo configuration screen, or learn more in the docs.

Qodo Logo


useEffect(() => {
if (disabled || !isVisible) return;
if (disabled || !isVisible || prefersReducedMotion) return;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

1. Missing effect dependency 🐞 Bug ≡ Correctness

GlowingEffect’s event-listener useEffect reads prefersReducedMotion but omits it from the
dependency array, so changes to the reduced-motion preference won’t re-run the effect and may leave
listeners (and associated animation work) active until some other dependency changes or the
component unmounts. This is also the pattern flagged by the React hooks exhaustive-deps rule
typically enabled via next/core-web-vitals.
Agent Prompt
### Issue description
`prefersReducedMotion` is used inside the event-listener `useEffect` but is not included in its dependency array. As a result, toggling the OS-level reduced-motion preference while the component is mounted will not trigger effect cleanup/re-subscription.

### Issue Context
- `prefersReducedMotion` is read via `useReducedMotion()`.
- The effect condition now early-returns when `prefersReducedMotion` is true.
- The effect dependency list currently does not include `prefersReducedMotion`.

### Fix Focus Areas
- components/ui/glowing-effect.tsx[117-135]

### Proposed fix
Update the dependency array to include `prefersReducedMotion` so the effect cleans up listeners when the preference changes:

```ts
useEffect(() => {
  if (disabled || !isVisible || prefersReducedMotion) return;
  // ...
}, [handleMove, disabled, isVisible, prefersReducedMotion]);
```

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Accessibility] Respect prefers-reduced-motion for all CSS animations

2 participants