Native

The native style runtime and the integrations that make Tamagui feel native on iOS and Android

Tamagui renders real native views on iOS and Android, with the same components, tokens, and themes you use on web. Nothing extra is required to get started. The runtime and integrations below make it faster and more native once you need them.

Configure Tamagui with the same provider on native and web.

Native runtime

The native runtime is in beta. It needs React Native 0.82 or newer with the New Architecture (Fabric).

By default, a theme change or a style update re-renders the React components that use it. @tamagui/native-registry moves that work into a small C++ registry. It links eligible native views to the registry and applies theme and style updates directly through Fabric, so the components don’t re-render.

Install it along with Nitro Modules, then rebuild your iOS and Android binaries:

yarn add @tamagui/native-registry react-native-nitro-modules

Turn on the compiler output in tamagui.build.ts:

import type { TamaguiBuildOptions } from 'tamagui'
export default {
config: './tamagui.config.ts',
components: ['tamagui'],
experimental: {
nativeFastPath: true,
},
} satisfies TamaguiBuildOptions

Register the style engine once at startup, before your app tree mounts:

import * as nativeRegistry from '@tamagui/native-registry'
import { setNativeStyleEngine } from 'tamagui'
if (nativeRegistry.isAvailable()) {
setNativeStyleEngine(nativeRegistry)
}

If the native module isn’t available on a platform, components render the normal React way, so the same code is safe everywhere.

Integrations

@tamagui/native connects Tamagui to popular React Native libraries. Each integration is opt-in: install the library, then import its setup module at your app entry point, before any Tamagui imports.

yarn add @tamagui/native
IntegrationLibraryWhat you get
Gesture Handlerreact-native-gesture-handlerCorrect nested presses, native Sheet drag and scroll
Portalsreact-native-teleportPortaled content that keeps your React context
Reanimatedreact-native-reanimatedAnimations on the UI thread
Keyboard Controllerreact-native-keyboard-controllerSheets that track the keyboard frame by frame
Safe Areareact-native-safe-area-contextThe safe style value and safe area aware sheets
Menus@expo/ui or zeegoPlatform menus and context menus
ToastburntSystem toasts and indicators
Integration
Gesture Handler
Library
react-native-gesture-handler
What you get
Correct nested presses, native Sheet drag and scroll
Integration
Portals
Library
react-native-teleport
What you get
Portaled content that keeps your React context
Integration
Reanimated
Library
react-native-reanimated
What you get
Animations on the UI thread
Library
react-native-keyboard-controller
What you get
Sheets that track the keyboard frame by frame
Integration
Safe Area
Library
react-native-safe-area-context
What you get
The safe style value and safe area aware sheets
Integration
Menus
Library
@expo/ui or zeego
What you get
Platform menus and context menus
Integration
Toast
Library
burnt
What you get
System toasts and indicators

A typical entry file with several integrations looks like this:

// index.js, before any tamagui imports
import '@tamagui/native/setup-gesture-handler'
import '@tamagui/native/setup-teleport'
import '@tamagui/native/setup-keyboard-controller'
import '@tamagui/native/setup-safe-area'

In a monorepo, install the native libraries in the app package, since native modules are linked from there.

Gesture Handler

With React Native Gesture Handler, onPress works the way React Native Pressable does: when pressables are nested, only the innermost one fires. Sheet also uses it to hand off smoothly between dragging the sheet and scrolling its content.

yarn add react-native-gesture-handler
import '@tamagui/native/setup-gesture-handler'
import { GestureHandlerRootView } from 'react-native-gesture-handler'
export default function App() {
return (
<GestureHandlerRootView style={{ flex: 1 }}>{/* your app */}</GestureHandlerRootView>
)
}

To use it for only one of the two, call setupGestureHandler instead:

import { setupGestureHandler } from '@tamagui/native/setup-gesture-handler'
// presses only, Sheet keeps its default gestures
setupGestureHandler({ pressEvents: true, sheet: false })

With press events enabled, onPress calls stopPropagation() on web as well, so nested pressables behave the same on both platforms. Use onClick on web when you want normal DOM bubbling.

PressBoundary

Tamagui pressables coordinate with each other automatically. A React Native Pressable or a custom native wrapper doesn’t take part in that, so tapping it can also fire a Tamagui pressable around it. Wrap it in PressBoundary to stop that:

import { PressBoundary } from '@tamagui/native/gesture-handler'
import { Pressable } from 'react-native'
export function AvatarLink() {
return (
<PressBoundary debugName="AvatarLink">
<Pressable onPress={() => {}}>{/* your content */}</Pressable>
</PressBoundary>
)
}

PressBoundary only affects Tamagui pressables. It doesn’t change how React Native events bubble in general. Native Menu.Trigger and ContextMenu.Trigger already do this for you.

Portals

On native, Tamagui’s default portal re-mounts content at the root, which cuts it off from the React context it was declared in. With react-native-teleport, content inside Sheet, Dialog, Popover, and other portaled components keeps your contexts, such as navigation and app state.

yarn add react-native-teleport
import '@tamagui/native/setup-teleport'

In a One app, One’s native portals do the same job without another native library:

import '@tamagui/native/setup-one-portal'

Use one of the two setups, not both.

See Portal for details.

Reanimated

Reanimated runs animations on the UI thread, so they stay smooth while JavaScript is busy. It plugs in as an animation driver, so it needs no setup import:

yarn add @tamagui/animations-reanimated react-native-reanimated
import { createAnimations } from '@tamagui/animations-reanimated'

See the Reanimated driver for configuration, and animation drivers to use it on native with a lighter driver on web.

Keyboard Controller

With react-native-keyboard-controller, Sheet follows the keyboard frame by frame as it opens and closes, and dragging the sheet can dismiss the keyboard interactively.

yarn add react-native-keyboard-controller
import '@tamagui/native/setup-keyboard-controller'
import { KeyboardProvider } from 'react-native-keyboard-controller'
export default function App() {
return <KeyboardProvider>{/* your app */}</KeyboardProvider>
}

Safe Area

This setup lets styles use the device safe area insets, and Sheet uses it to stay clear of the notch and home indicator.

yarn add react-native-safe-area-context
import '@tamagui/native/setup-safe-area'
import { SafeAreaProvider } from 'react-native-safe-area-context'
import { TamaguiProvider } from 'tamagui'
export default function App() {
return (
<SafeAreaProvider>
<TamaguiProvider config={config}>{/* your app */}</TamaguiProvider>
</SafeAreaProvider>
)
}

SafeAreaProvider has to wrap TamaguiProvider. Then pass safe to any padding or margin prop, or to top, bottom, left, right, and inset:

<View className="pt-[safe] pb-[safe]" />;

On web, safe becomes the CSS env(safe-area-inset-*) value, so the same code works in the browser without this setup.

Menu and ContextMenu can render the platform’s own menus, with system haptics and blur, when you pass the native prop.

yarn add @expo/ui
import '@tamagui/native/setup-expo-ui-menu'
<Menu native>{/* menu content */}</Menu>

The Zeego adapter is still available for apps on React Native versions it supports:

yarn add zeego @react-native-menu/menu react-native-ios-context-menu react-native-ios-utilities sf-symbols-typescript
import '@tamagui/native/setup-zeego'

See Menu and ContextMenu.

Toast

With burnt, toasts use the system UI: SPIndicator on iOS, ToastAndroid on Android, and the Notification API on web.

yarn add burnt
import '@tamagui/native/setup-burnt'
<Toast.Provider native>{/* your app */}</Toast.Provider>

See Toast.