skip to content

In React Native, what do onMagicTap and onAccessibilityEscape do, and where would you use them in an email app?

level: middleimportance: nice to knowfreq 12%

answer

  1. two iOS-only VoiceOver gestures
  2. two-finger double-tap: magic tap
  3. two-finger scrub, Z shape: escape
  4. the system walks up the hierarchy
  5. no Android equivalent in these props

basics

~20 s

They are iOS-only VoiceOver gesture handlers: onMagicTap runs on a two-finger double-tap for the screen's most important action, such as sending a draft, and onAccessibilityEscape runs on a two-finger scrub to go back or dismiss, such as closing the compose sheet.

solid answer

~40 s

Both are iOS-only props on `View` that answer VoiceOver's global gestures. `onMagicTap` fires on a two-finger double-tap and should perform the most relevant action for the current context; in an email app, sending the draft on the compose screen, or playing and pausing a voice attachment. `onAccessibilityEscape` fires on the two-finger scrub, a Z-shaped gesture, and should move back: dismiss the compose sheet or a custom dialog, or return to the list. If the focused element has no handler, iOS walks up the view hierarchy until it finds a view that has one, so I put them on the screen or sheet container rather than on every child. TalkBack has no counterpart in these props, so the same operations still need visible controls and, on Android, the platform back behaviour.

code

tsx · 22 lines
tsx
import {Pressable, Text, TextInput, View} from 'react-native';

type ComposeProps = {
  body: string;
  onChangeBody: (text: string) => void;
  onSend: () => void;
  onClose: () => void;
};

export function ComposeSheet({body, onChangeBody, onSend, onClose}: ComposeProps) {
  return (
    <View style={{flex: 1}} onMagicTap={onSend} onAccessibilityEscape={onClose}>
      <TextInput value={body} onChangeText={onChangeBody} multiline accessibilityLabel="Message body" />
      <Pressable onPress={onSend}>
        <Text>Send</Text>
      </Pressable>
      <Pressable onPress={onClose}>
        <Text>Close</Text>
      </Pressable>
    </View>
  );
}

go deeper

for a junior

Recall that onMagicTap and onAccessibilityEscape are iOS-only VoiceOver gesture handlers for the main action and for going back.

for a middle

Explain the two gestures, how iOS walks up the hierarchy to find a handler, and why the props belong on screen or sheet containers.

for a senior

Choose sensible magic-tap and escape meanings per screen, guard destructive outcomes, and keep visible controls as the primary path on both platforms.

for a principal

Decide whether shells such as screens and sheets provide these handlers by default, so escape and magic tap behave the same across the whole app.

## Two VoiceOver gestures with a job **VoiceOver**, the iOS screen reader, reserves several multi-finger gestures that work anywhere. Two of them are meant for apps to handle, and React Native exposes both as props on `View`: | Prop | Gesture | Intended meaning | |---|---|---| | `onMagicTap` | two-finger double-tap | perform the most relevant action for the current context | | `onAccessibilityEscape` | two-finger scrub (a Z shape) | move back: dismiss, close, or go up one level | Both are **iOS-only**. TalkBack on Android has no counterpart in these props, so they add convenience for VoiceOver users without replacing any control. ## How the handler is found The user performs the gesture while VoiceOver's focus is on some element. iOS first asks that element; if it has no handler, the system **walks up the view hierarchy** until it finds a view that does. For escape, the React Native docs note that when no view handles it, VoiceOver plays a short error sound instead. That lookup is why these props belong on **containers**: - Put `onMagicTap` on the compose screen's root `View`, and a two-finger double-tap anywhere on that screen sends the draft. - Put `onAccessibilityEscape` on the custom sheet's outer `View`, and the scrub dismisses it from any element inside. - A handler on a deeper view wins for focus inside that view, which lets a sub-area override the screen-level meaning. ## What to wire in an email app - **Compose screen, magic tap: send.** It is the one action the screen exists for. Pair it with clear speech about the result, because a sent email cannot be taken back easily; if the draft is incomplete, say so instead of sending. - **Voice-message attachment, magic tap: play or pause.** A toggle is the kind of main action the gesture is meant for. - **Compose sheet or custom dialog, escape: dismiss.** Ask for confirmation first if unsent text would be lost. - **Message detail pushed over the inbox, escape: go back** to the list, mirroring the back button. ## Rules for using them well 1. **Never make them the only path.** Every magic-tap and escape operation must also exist as a visible, labelled control, because many VoiceOver users never use these gestures and TalkBack users cannot. 2. **Keep the meaning obvious.** Magic tap should do the thing a user would guess is the main action; a surprising magic tap is worse than none. 3. **Avoid destructive surprises.** Magic tap that deletes, or escape that throws away a long draft without asking, turns a shortcut into a trap. 4. **Scope carefully.** A screen-level handler applies wherever focus is on that screen, including inside lists, so check that nested areas do not need a different meaning. ## Related action names The React Native docs also list `magicTap` and `escape` among the standard names for `accessibilityActions`, both iOS-only. For a screen or a sheet, the dedicated `onMagicTap` and `onAccessibilityEscape` props are the direct way to handle the gestures, while `accessibilityActions` is where row-level custom operations such as archive belong. ## Verifying With VoiceOver on, focus any element on the compose screen and double-tap with two fingers; the draft should send, and the app should say so. Open the sheet, focus a button inside it, and scrub; the sheet should close and focus should return to a sensible place. There is no automated substitute for performing the gestures on a device.

  • Why put onAccessibilityEscape on the sheet container instead of on its Close button?
    iOS asks the focused element first and then walks up the hierarchy until a view has a handler. On the container, the scrub dismisses the sheet whichever element inside it has focus; on the Close button, it would only work while that button is focused, which defeats the point of a global gesture.

saying these in an interview costs you the question

  • onMagicTap also fires for TalkBack users on Android.
  • Magic tap can replace the visible Send button for screen-reader users.
  • onAccessibilityEscape must be on the exact element that has focus to fire.
  • Magic tap is a two-finger triple-tap gesture on iOS.
  • Escape should perform the screen's main action, like magic tap.