npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

react-native-touch-control

v0.2.1

Published

Prevent iOS scroll-to-top on tappable areas in React Native

Readme

RN Native Touch Control

A React Native view backed by a real native iOS UIControl. Its presence in the view hierarchy makes iOS treat the region as an interactive control, so system behaviors that defer to controls skip over it.

The primary use case is scroll-to-top. On iOS, tapping the status bar scrolls the nearest scroll view to the top. It also fires when the tap lands on a button in the top bar, so the button press and the scroll happen at once. On iPad it can be worse: if the button navigates back, the scroll hits the previous screen's scroll view instead of the current one. Hosting the button in a NativeTouchControl opts the region out.

iOS 26+

The scroll-to-top tap target used to be the status bar only, a thin strip that rarely overlapped interactive UI. iOS 26 extended it over much of the navigation bar, where apps commonly place back buttons, titles, and other controls.

Taps meant for nav-bar controls now often land inside the scroll-to-top zone and trigger an unwanted scroll. NativeTouchControl opts those regions out.

[!NOTE] This is an iOS-only behavior. On Android the component is a plain passthrough container (see Platform support).

Demo

The nav-bar buttons sit in the status-bar tap zone. Each hosts a NativeTouchControl on top of its content, so pressing them does not scroll the list to the top. The example uses both the wrapper and the overlay-sibling forms. A runnable version lives in example/.

Installation

npm install react-native-touch-control

Then install pods (or let your build do it):

cd ios && pod install

This library uses the New Architecture (Fabric). No extra linking steps are required beyond autolinking.

Usage

NativeTouchControl accepts all standard View props and lays out like any other view. There are two equally valid ways to use it. Both place the native UIControl on top of your content, which is the condition iOS's scroll-to-top detection checks (see How it works).

Use case 1: as a wrapper

Nest content inside NativeTouchControl. The control spans the region, and its children render beneath it:

import { NativeTouchControl } from 'react-native-touch-control';

function TopBarButton({ label, onPress }) {
  return (
    <Pressable onPress={onPress}>
      {/* UIControl on top of the label, so iOS skips scroll-to-top here. */}
      <NativeTouchControl>
        <Text>{label}</Text>
      </NativeTouchControl>
    </Pressable>
  );
}

Use this form when the control should cover exactly the content.

Use case 2: as an overlay sibling

Render NativeTouchControl as a StyleSheet.absoluteFill sibling of the content, placed last so it stays topmost. This covers the whole parent, including any padding a wrapper would leave exposed. It has no children, so render it only on iOS:

import { Platform, StyleSheet } from 'react-native';

<Pressable style={styles.button} onPress={onPress}>
  <Text style={styles.label}>{label}</Text>
  {/* Fills the whole Pressable, padding included. */}
  {Platform.OS === 'ios' && (
    <NativeTouchControl style={StyleSheet.absoluteFill} />
  )}
</Pressable>;

Use this form when the tappable area is larger than the content itself (padding, hit slop, a whole card).

NativeTouchControl must be inside the Pressable

In both use cases, the Pressable/Touchable must be an ancestor of NativeTouchControl. Nest the control inside it, not around it. React Native routes the tap up the tree to the responder, so a Pressable inside NativeTouchControl never fires onPress.

// ✅ Works — Pressable is an ancestor of NativeTouchControl
<Pressable onPress={onPress}>
  <NativeTouchControl>
    <Text>{label}</Text>
  </NativeTouchControl>
</Pressable>

// ❌ Never fires onPress — Pressable is a descendant
<NativeTouchControl>
  <Pressable onPress={onPress}>
    <Text>{label}</Text>
  </Pressable>
</NativeTouchControl>

How it works

React Native and iOS run two independent touch systems. RN's responder system handles Pressable, Touchable*, and gesture handlers in JavaScript, while UIKit dispatches touches to native views in parallel. The two do not coordinate, so handling a press in JS does not stop UIKit from also acting on the same tap. That is why an ordinary Pressable in the nav bar still triggers scroll-to-top: to UIKit there is no native control there, only a tap in the scroll-to-top zone.

On iOS, scroll-to-top is driven by a private gesture recognizer, _UIDoubleTapInteractionGestureRecognizer, that watches the status-bar region. Before scrolling, it hit-tests the touch and checks whether the topmost view there is an interactive UIControl. If it is, the recognizer assumes the tap belongs to that control and does not scroll.

NativeTouchControl relies on this check. The native view is a plain UIControl with no target/action, a passive marker whose presence as the topmost view is enough for the recognizer to skip scroll-to-top.

The two use cases are equivalent because both put the UIControl on top of the content at the tapped point: beneath the content in the wrapper form, in front of it in the overlay form. Either way the hit-test resolves to the UIControl. Since this is a general UIKit hit-test and not a scroll-to-top-specific hook, the same approach opts a region out of other system behaviors that defer to UIControls.

Two implementation details:

  • The control is stretched to the view's full bounds, padding included, so no uncovered edge is left where a tap could reach the system behavior.
  • React children mount beneath the control, so transparent regions show the content through while the control overlays the whole area.

Platform support

| Platform | Behavior | | -------- | --------------------------------------------------------------------------------------------------------------------------- | | iOS | Renders a native UIControl region that opts the area out of UIControl-deferring system behaviors such as scroll-to-top. | | Android | No-op passthrough. Renders its children like an ordinary view. There is no equivalent scroll-to-top behavior to suppress. |

Because it is a no-op on Android, the component can be used in cross-platform code without branching.

Contributing

License

MIT


Made with create-react-native-library