Open-source project
enzomanuelmangano/pressto avatar
enzomanuelmangano/pressto

Pressto: animated React Native pressables that run on the UI thread

Your React Native app deserves better tap interactions.

846 stars11 forksTypeScriptMIT

At a glance

What is it?
Pressto is a small React Native library that replaces TouchableOpacity with animated pressables driven by Reanimated worklets. It ships two components, a factory for custom animations and a global configuration provider, but it also adds several peer dependencies and a worklet directive you can forget.
Who is it for?
Pressto suits teams that already run Reanimated and Gesture Handler and want one press behaviour across the app rather than per-screen styling. It is the wrong choice for a project that cannot take on react-native-worklets and react-native-reanimated as peers, and for anyone unwilling to keep the 'worklet' directive in front of every custom animation function.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 21 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 25, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What Pressto replaces and who it is for

The one line pitch in the README is "Replace TouchableOpacity with animated pressables that run on the main thread." That is the whole problem statement. A React Native app that ships on both iOS and Android usually has press feedback defined in a dozen places: a TouchableOpacity here, a Pressable with an opacity style there, a scale transform copied into a shared button component. Each one runs its animation on the JavaScript thread, so the feedback can stutter when the bridge is busy.

Pressto targets the specific case where that feedback is uniform enough to define once. The audience is not someone discovering animation, it is a developer who already knows that a press should look the same everywhere and wants it to be cheap. The library is written in TypeScript, published under the MIT licence, and built on react-native-gesture-handler and react-native-reanimated.

Two details frame the trade-off immediately. The package description in package.json is plain: "Some custom react native touchables". And the install command pulls four packages rather than one, so this is not a drop-in dependency you can add to a project without touching its peer setup.

Installation pulls three peer packages along with it

The installation section of the README is a single command, and it is worth reading closely because it names everything the library needs at runtime.

sh
bun add pressto react-native-reanimated react-native-gesture-handler react-native-worklets

The command uses bun rather than npm or yarn, but the four package names are the meaningful part. A project that has not adopted react-native-reanimated and react-native-gesture-handler yet is not adding a small utility, it is adding the animation and gesture stack that those libraries require, and react-native-worklets on top of it.

That is the main adoption cost. The library itself is small and TypeScript-first, with a source entry at ./src/index.tsx, commonjs and module builds, generated type declarations, and a build driven by bob. But the ecosystem it plugs into is the real dependency.

The repository also ships an eslint-plugin-pressto in its own directory. The README points at it as a way to catch a missing worklet directive at development time rather than at the moment an animation silently fails to move.

The two ready-made components and their props

Pressto exports two components that cover the common cases: PressableScale, which scales down when pressed, and PressableOpacity, which fades. Both accept the standard React Native Pressable props, including onPress, onPressIn, onPressOut and style, so they can replace a TouchableOpacity in place.

jsx
import { PressableScale } from 'pressto';

<PressableScale onPress={() => alert('Pressed!')}>
  <Text>Scale Animation</Text>
</PressableScale>;

Swapping one for the other is the whole quickstart. The README's own example is short enough to copy without adaptation.

The naming is a bit confusing on first contact, since PressableOpacity here does not share the name of the React Native component it is meant to replace. That is a naming convention the library has chosen, and it is consistent enough to be learnable, but it is worth knowing before you search the codebase for a fade behaviour.

Per component configuration is available too. The config prop shallow-merges over whatever PressablesConfig supplied, so a single button can override just activeOpacity or just minScale without restating the rest.

Custom animations need a worklet directive you cannot skip

The factory createAnimatedPressable takes a function that receives a progress value and returns a style object. The progress parameter runs from 0 while idle to 1 while pressed, which means any style property can be interpolated.

jsx
import { createAnimatedPressable } from 'pressto';

const PressableRotate = createAnimatedPressable((progress) => {
  'worklet';
  return {
    transform: [{ rotate: `${progress * 45}deg` }],
  };
});

The catch is stated in bold in the README: the 'worklet' directive is required at the start of the animation function, and without it the animation will not run on the UI thread. The author treats this as the single most likely way to get started badly, and ships an eslint plugin for it.

The advanced form takes a second argument, options, which exposes isPressed, isToggled and isSelected. isPressed is true while a finger is down, isToggled flips on every press and stays, and isSelected marks the last pressed item in a group. Those three cover the common toggle and segmented control cases without any extra state in the component.

Theme values can also be passed through to worklets. A PressablesConfig can carry a metadata object, and a generic parameter on createAnimatedPressable gives the shape of that object to the worklet with type checking, which is how a design system's colours and spacing reach a function that runs outside the JavaScript thread.

PressablesConfig as the app-wide control point

PressablesConfig is a provider that sets behaviour for everything beneath it. It takes an animationType of 'timing' or 'spring', with 'timing' as the default, plus an animationConfig object passed straight to the chosen animation, and a config object holding minScale, activeOpacity and baseScale.

jsx
<PressablesConfig
  animationType="spring"
  animationConfig={{ damping: 30, stiffness: 200 }}
  config={{ minScale: 0.9, activeOpacity: 0.6 }}
>
  <PressableScale onPress={() => console.log('pressed')}>
    <Text>Now with spring animation!</Text>
  </PressableScale>
</PressablesConfig>

A second responsibility is defaultProps, which sets React Native pressable props for every descendant. The README's example disables the Android ripple with rippleColor 'transparent' for all pressables while letting one child set rippleColor back to 'blue'. The documented prop list covers rippleColor, rippleRadius, touchSoundDisabled, hitSlop and accessibility props.

Global handlers are the third piece. A globalHandlers object receives onPress, onPressIn and onPressOut callbacks for every pressable below the provider, which is a convenient place for haptic feedback through expo-haptics. A pressable can opt out with skipGlobalHandlers, and its own onPress, onPressIn and onPressOut still fire.

One detail deserves care. Per component metadata replaces the provider level metadata rather than merging with it, so combining a theme with a component name means spreading them yourself, as the README shows with metadata={{ ...theme, name: 'checkout' }}. Silently losing the theme because you added a name is an easy mistake to make and a confusing one to debug.

Where Pressto is the wrong tool

The clearest limitation is the peer dependency set. Pressto cannot work without react-native-reanimated, react-native-gesture-handler and react-native-worklets, and a project on an older React Native version that cannot take those on is out of scope. The library is at version 0.7.0 in package.json, and the recent releases list includes 0.7.0-beta.0 and 0.7.0-beta.1 before the stable tag, which is a signal that the API surface may still shift between minor versions.

The second limitation is the worklet directive. It is a bare string literal at the top of a function that most editors and type checkers do not warn about by default. Forget it and the animation silently does not run on the UI thread, which looks like a performance bug rather than a missing line. The eslint plugin exists for exactly this, and the README recommends installing it, but it only helps if the project adopts it.

The third limitation is scope. Pressto handles press feedback, and nothing else. There is no layout animation, no shared transition, no gesture recognition of its own. A team looking for a general purpose animation library will find this too narrow.

A fourth point is quieter. Because the components are built on the BaseButton from react-native-gesture-handler, tap conflict detection inside scrollable lists is handled by the underlying library, and the README's section on that is cut off mid-sentence in the version available here. The behaviour is claimed, but the details of how conflicts are resolved are not documented on this page.

How Pressto differs from plain Pressable and TouchableOpacity

The closest alternative is React Native's own Pressable, which already accepts a style function receiving a pressed boolean and can therefore be animated with Reanimated without any wrapper library. The difference in approach is where the configuration lives. With plain Pressable, the pressed state and the interpolation live in each screen, and the shared behaviour ends up in a hand written button component that every team has to write slightly differently.

Pressto moves that into two layers. The factory turns a progress-driven style function into a component, and PressablesConfig supplies app-wide defaults, global handlers and shared metadata on top. The second layer is what makes it more than an animation helper: it is a place to define press behaviour once for the whole app.

Compared with TouchableOpacity, the difference is mainly the thread the animation runs on. TouchableOpacity fades its own child on the JavaScript thread, so the fade competes with whatever else the JS thread is doing. Pressto runs the interpolation as a Reanimated worklet, which is the whole reason for the peer dependencies and the reason for the worklet directive.

There is also a broader difference between this library and full gesture libraries such as react-native-gesture-handler on its own. Gesture Handler offers pan, pinch and swipe recognisers and a lot of configuration surface. Pressto deliberately covers only the press case and inherits the tap handling from BaseButton, which is a smaller thing to adopt and a smaller thing to learn.

Maintenance, licensing and upgrade cost

The repository is not archived and the last push was on 2026-10-07, so changes are landing. The latest tagged release is v0.7.0 from 2026-06-12, which puts it a few months behind the tip of main, meaning published versions lag the source tree.

The licence is MIT, and the licence file sits at the root of the repository. That is a permissive licence with no obligations beyond keeping the copyright notice and licence text with copies of the software. It applies to the library code, not to any animations or screens built on top of it.

The upgrade cost is the part worth thinking about. Because the library sits directly on react-native-reanimated and react-native-worklets, upgrading Pressto means keeping three related packages in step, and a Reanimated major upgrade has historically required changes to worklet setup. The eslint-plugin-pressto also needs to be kept aligned with the runtime version, since it checks for a directive whose meaning is defined by the worklets runtime.

The repository ships a bun.lock file and scripts for test, typecheck, lint, example and release, plus a lefthook.yml for commit hooks, so the project itself is set up for contributors to work on it.

Editorial conclusion

Pressto suits teams that already run Reanimated and Gesture Handler and want one press behaviour across the app rather than per-screen styling. It is the wrong choice for a project that cannot take on react-native-worklets and react-native-reanimated as peers, and for anyone unwilling to keep the 'worklet' directive in front of every custom animation function. Before adopting it, confirm that the installed versions of react-native-reanimated, react-native-gesture-handler and react-native-worklets match what your app already pins, since the library installs all three alongside itself.

Frequently asked questions

How do I install Pressto in a React Native project?

The README gives one command: `bun add pressto react-native-reanimated react-native-gesture-handler react-native-worklets`. The three companion packages are required, because Pressto is built on Reanimated and Gesture Handler and depends on react-native-worklets.

What happens if I forget the 'worklet' directive in a Pressto animation?

The README states that the directive is required at the start of your animation function, and that without it the animation will not run on the UI thread. The project ships eslint-plugin-pressto to catch the omission at development time.

Can I set a different press animation for every pressable in an app with Pressto?

Yes. PressablesConfig sets app-wide defaults through animationType, animationConfig and config, and a per component config prop shallow-merges over those values, so a single pressable can change just minScale or just activeOpacity.

Does Pressto support hover animations on the web?

Yes, on web only. The activateOnHover prop can be set per component or on PressablesConfig to trigger the animation on hover.

What licence does Pressto use?

The package.json in the repository lists the licence as MIT, and a LICENSE file is present at the root of the repository.

How do I add haptic feedback to every Pressto button?

Pass a globalHandlers object with an onPress callback to PressablesConfig, for example one that calls Haptics.selectionAsync from expo-haptics. A single pressable can opt out with skipGlobalHandlers while its own onPress still fires.

Official sources

  1. enzomanuelmangano/pressto on GitHub
  2. Issues
  3. License: MIT
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/enzomanuelmangano-pressto.svg)](https://hysenlabs.com/projects/enzomanuelmangano-pressto)

Was this useful?