RORK LABJP
TOOLING — Rork's developer repos keep moving: rork-xcode was updated on July 16, rork-device on July 15, and rork-plist on July 13OPUS46 — Claude Opus 4.6 is live in Rork, and Rork Max is built to assemble apps on top of Claude CodeSIM — A cloud iOS simulator runs in the browser, with one click to install on a device and two clicks to publish to the App StoreMAX — Rork Max emits pure Swift rather than React Native, reaching iPhone, iPad, Apple Watch, Apple TV, Vision Pro, and even iMessageNATIVE — That opens up HealthKit, ARKit and LiDAR, NFC, Dynamic Island, Live Activities, 3D through Metal, and on-device inference with Core MLSEED — Rork raised a $15M seed led by Left Lane Capital, with Peak XV and a16z Speedrun joining the roundTOOLING — Rork's developer repos keep moving: rork-xcode was updated on July 16, rork-device on July 15, and rork-plist on July 13OPUS46 — Claude Opus 4.6 is live in Rork, and Rork Max is built to assemble apps on top of Claude CodeSIM — A cloud iOS simulator runs in the browser, with one click to install on a device and two clicks to publish to the App StoreMAX — Rork Max emits pure Swift rather than React Native, reaching iPhone, iPad, Apple Watch, Apple TV, Vision Pro, and even iMessageNATIVE — That opens up HealthKit, ARKit and LiDAR, NFC, Dynamic Island, Live Activities, 3D through Metal, and on-device inference with Core MLSEED — Rork raised a $15M seed led by Left Lane Capital, with Peak XV and a16z Speedrun joining the round
Articles/Dev Tools
Dev Tools/2026-05-20Advanced

Production-Quality VoiceOver and Dynamic Type for Rork Apps

An indie developer's implementation notes for adding production-quality VoiceOver, Dynamic Type, and Reduce Motion support to React Native apps generated by Rork — covering label design, focus management, and regression testing.

accessibilityVoiceOverDynamic TypeRork515React Native209Reduce Motion2

Premium Article

About six months after a wallpaper app of mine had been live, a two-star review showed up: "When VoiceOver reads this app, all I hear is 'button, button, button.'" I have been shipping personal apps since 2014, and across iOS and Android the catalog has just edged past 50 million downloads — yet accessibility was sitting on my "do it properly someday" list, and had been for over six years.

I turned VoiceOver on and walked through the app myself. What looked finished visually was, in audio, nothing more than disjointed fragments. The icon buttons had no accessibilityLabel, the card rows announced their tap targets and text content as separate items, and the toggles in Settings never said whether they were on or off. VoiceOver is not a tool for some other world; it is an inspection device that bluntly tells you what your screens are failing to communicate.

Since then, whenever I start a new Rork project, I spend the first three days entirely on VoiceOver and Dynamic Type. Designing accessibility in is dramatically faster than retrofitting it, and it improves the overall implementation as a side effect. Here are the notes I keep open whenever I add accessibility to a Rork-generated React Native + Expo project — focused on label design, focus management, and regression testing, the parts the official docs cover only in fragments.

Three Ways Accessibility Pays Off for an Indie Developer

I used to assume accessibility was an ethical "should" that I could not realistically afford given my time budget. After actually doing the work, I noticed three concrete payoffs.

The first is lower store-review risk. Apple rarely rejects outright for accessibility, but Google Play's Pre-launch Report flags accessibility issues as warnings. In 2024 I had one release where contrast and missing voice labels alone added 48 hours to the review queue. For an indie release with a scheduled launch date, that delay stings emotionally as much as practically.

The second is fewer Dynamic Type complaints in store reviews. In Japan, where my wallpaper apps have a strong middle-aged user base, a non-trivial share of users runs system text at "Larger" or the Accessibility sizes. My GA4 data shows roughly 7.2 % of sessions arriving with an Accessibility content size category set. When text gets clipped for those users, low-star reviews follow immediately. In the three months after I shipped a full Dynamic Type pass, my average rating climbed from 4.2 to 4.5.

The third payoff is that VoiceOver work doubles as a structural review of your UI. Sorting out the read order surfaces logic gaps that looked fine visually — redundant regions, decorations that look tappable, opaque icons. Cleaning those up makes Dynamic Type, localization, and any future iPadOS adaptation much cheaper. Accessibility is an outward-facing principle and, at the same time, an inward-facing forced design review.

Aligning Visual Cards with Voice Cards

The most common reason VoiceOver reading breaks is that something that visually reads as a single card gets split into multiple independent elements in audio. Suppose Rork generates a product card like this:

// Before: VoiceOver makes you swipe four times per card
<Pressable onPress={() => navigate('Detail', { id })}>
  <Image source={{ uri: thumbnail }} style={styles.thumb} />
  <Text style={styles.title}>{title}</Text>
  <Text style={styles.price}>{`¥${price.toLocaleString()}`}</Text>
  <View style={styles.badge}>
    <Text style={styles.badgeText}>NEW</Text>
  </View>
</Pressable>

VoiceOver treats the Image, the two Text nodes, and the badge as four separate elements. The user has to swipe four times before the card is fully announced — information that the sighted user takes in at a glance gets atomized into a four-step audio walkthrough.

The fix is to roll this into one voice card:

// After: one swipe announces the whole card as a coherent unit
<Pressable
  onPress={() => navigate('Detail', { id })}
  accessible={true}
  accessibilityRole="button"
  accessibilityLabel={`${title}, ${price.toLocaleString()} yen${isNew ? ', new' : ''}`}
  accessibilityHint="Double tap to open details"
>
  <Image
    source={{ uri: thumbnail }}
    style={styles.thumb}
    accessible={false} // parent handles the announcement
  />
  <Text style={styles.title} accessible={false}>{title}</Text>
  <Text style={styles.price} accessible={false}>{`¥${price.toLocaleString()}`}</Text>
  {isNew && (
    <View style={styles.badge} accessible={false}>
      <Text style={styles.badgeText}>NEW</Text>
    </View>
  )}
</Pressable>

Three things matter here. The parent Pressable gets accessible={true} and a combined accessibilityLabel, while the children are explicitly marked accessible={false}. The accessibilityRole="button" tells VoiceOver to announce "button" after the label so users know it is interactive. The accessibilityHint adds "what happens on double-tap" without duplicating the label — users who disable hints in VoiceOver settings simply never hear it.

I also spell out numbers in the label. Passing ¥1,200 straight through is a coin toss: depending on locale settings and the screen reader, you sometimes get "yen, one, point, two hundred." Apple often reads currency correctly, but TalkBack on Android does not always cooperate. In production I just write the value out in words within the label — ${price.toLocaleString()} yen instead of ¥${price}.

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
How to align visual cards with voice cards using accessibilityElements so VoiceOver reads each card as one unit
Sizing math and a sensible maxFontSizeMultiplier cap that survives the full 100%–310% Dynamic Type range without breaking layout
Branching logic for swapping ad views and parallax when Reduce Motion or Reduce Transparency is enabled
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-07-15
The Comma That Fell to the Start of a Line: Rork, Quote Cards, and Japanese Line Breaking
React Native's Text component does not guarantee Japanese line-breaking rules. Here are the violation rates I measured, a WORD JOINER implementation that survives copy and VoiceOver, an onTextLayout audit harness, and how Rork Max (SwiftUI) differs.
Dev Tools2026-07-15
My Rork sleep timer faded out on time — but the sound didn't
Rork writes sleep-timer fades against the wall clock with setInterval. The numbers say 30 minutes, but the real audio fades early or cuts out abruptly. Here is how to drive the fade from the actual playback position, why a logarithmic curve sounds natural, and the honest limit of JS fades when the screen is locked.
Dev Tools2026-07-14
Designing Seams That Survive AI Regeneration in Rork
Every follow-up prompt to Rork can quietly wipe out logic you wrote by hand. Protecting it with prompts is a patch, not a fix. Here is how to separate generated code from code you own, and draw a boundary that regeneration cannot reach, with working Zustand and service-layer examples.
📚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 →