RORK LABJP
PRODUCT — Rork Max generates native Swift apps for iPhone, iPad, Apple Watch, Apple TV, Vision Pro, and iMessageNATIVE — Rork Max unlocks AR/LiDAR, Metal 3D games, Dynamic Island, Live Activities, HealthKit, and Core MLCLASSIC — The original Rork uses React Native (Expo), turning plain-English prompts into shippable iOS/Android appsFUNDING — Rork raised $2.8M from a16z (plus $15M more), reaching 743,000 monthly visits at 85% growthPRICING — Rork is free to start, with paid plans from $25/month; Rork Max is $200/monthCHOICE — Pick cross-platform Rork or Rork Max for deep Apple-native capabilities, depending on your goalPRODUCT — Rork Max generates native Swift apps for iPhone, iPad, Apple Watch, Apple TV, Vision Pro, and iMessageNATIVE — Rork Max unlocks AR/LiDAR, Metal 3D games, Dynamic Island, Live Activities, HealthKit, and Core MLCLASSIC — The original Rork uses React Native (Expo), turning plain-English prompts into shippable iOS/Android appsFUNDING — Rork raised $2.8M from a16z (plus $15M more), reaching 743,000 monthly visits at 85% growthPRICING — Rork is free to start, with paid plans from $25/month; Rork Max is $200/monthCHOICE — Pick cross-platform Rork or Rork Max for deep Apple-native capabilities, depending on your goal
Articles/Dev Tools
Dev Tools/2026-06-23Intermediate

What Your App Should Do When Someone Turns On Reduce Motion — A Motion-Respecting Layer in Expo

When a user enables iOS Reduce Motion or Android Remove Animations, how should your app respond? Combine AccessibilityInfo with Reanimated's ReduceMotion to replace heavy motion with a calmer alternative instead of simply switching it off.

Reduce Motion2Accessibility2Reanimated9Expo96React Native179

Premium Article

One day a review on a wallpaper app I maintain included a single line: "the slideshow makes me feel sick." Three stars. The note was short, but as an indie developer I sat with it for a while. The transition I had built as a cross-fade actually layered in a slight zoom and some parallax. For someone with Reduce Motion enabled, that movement was not pleasant — it was a burden.

When you run several apps in parallel on your own, you keep discovering this kind of "bug you can't see from your own device." Reduce Motion was a textbook case. Users with the setting on certainly exist, and our animations were reaching them too forcefully.

This article walks through how to build a layer that returns a gentler alternative based on the user's setting — not one that strips animation away wholesale — following an Expo + Reanimated implementation.

Where the device tells you about Reduce Motion

First, let's pin down how the OS exposes this preference.

On iOS it lives under Settings → Accessibility → Motion → Reduce Motion. On Android the closest equivalent is Settings → Accessibility → Remove animations. From React Native, both are read through the same AccessibilityInfo API.

import { AccessibilityInfo } from 'react-native';
 
// Read the current state once
const enabled = await AccessibilityInfo.isReduceMotionEnabled();

The easy thing to miss is that isReduceMotionEnabled() is an asynchronous API returning Promise<boolean>. You'll be tempted to read it synchronously, but the value isn't settled on the first render. Worse, users sometimes toggle the setting while your app is open — they jump to Settings, turn it on, and come back. To follow that round trip you need a subscription, not a one-time read.

A useReducedMotion hook that follows mid-session changes

You can subscribe to changes with AccessibilityInfo.addEventListener('reduceMotionChanged', ...). Let's wrap it in a small hook.

import { useEffect, useState } from 'react';
import { AccessibilityInfo } from 'react-native';
 
export function useReducedMotion(): boolean {
  const [reduced, setReduced] = useState(false);
 
  useEffect(() => {
    let mounted = true;
 
    // Read the initial value (async, so handle it with then, not await)
    AccessibilityInfo.isReduceMotionEnabled().then((value) => {
      if (mounted) setReduced(value);
    });
 
    // Follow toggles made while the app is running
    const sub = AccessibilityInfo.addEventListener(
      'reduceMotionChanged',
      (value) => setReduced(value),
    );
 
    return () => {
      mounted = false;
      sub.remove();
    };
  }, []);
 
  return reduced;
}

The mounted flag is there so that if the component unmounts before isReduceMotionEnabled() resolves, the later setReduced never fires. It's a small move, but it quietly prevents warnings on short-lived screens.

Note that since v3.5 Reanimated ships an official useReducedMotion() hook of the same name. If you already use Reanimated, reaching for that is the straightforward choice. I'm showing the hand-rolled version because understanding the mechanism lets you explain to yourself why the first value is false and why toggles are reflected.

// If you already use Reanimated, this is enough
import { useReducedMotion } from 'react-native-reanimated';

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
A useReducedMotion hook that subscribes to AccessibilityInfo and follows changes made while the app is running
Using Reanimated's ReduceMotion.System to tune behavior per animation
A decision table for replacing only parallax and large motion, rather than killing everything
Secure payment via Stripe · Cancel anytime

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Rork Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

Related Articles

Dev Tools2026-06-22
Build a Toast System in Your Rork App That Survives Overlaps, Screen Readers, and Notches
When you add toast notifications to a React Native app generated by Rork, the naive version breaks in three places: two toasts overlap, screen readers stay silent, and the text hides under the notch or home indicator. This walks through a root-level queue, animations decoupled from your app tree, AccessibilityInfo announcements, and safe-area placement — all in working code.
Dev Tools2026-06-23
Why Your Prices Show as “¥1234” on Some Phones — A Formatting Layer That Doesn't Trust Intl Alone in Expo
Same code, yet one device shows “¥1,480” and another “¥1480.” In Expo / Hermes, Intl leans on each device's locale data, so output can drift by OS and engine. Here is a resilient formatting layer that centralizes currency, number, date, and timezone handling and falls back gracefully where Intl is incomplete — with working code.
Dev Tools2026-06-23
The Private Screen That Lingers in the App Switcher — Hiding the Snapshot iOS Takes the Moment You Background Your App
When you send a React Native app generated by Rork to the background, iOS photographs the current screen for the App Switcher and writes it to disk. Journals and personal input screens linger there in plain sight. This walks through the iOS privacy overlay (why inactive, not background), Android's FLAG_SECURE, scoping it to sensitive screens only, and screenshot detection — all in working code.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →