Skip to content

Expo

Use this path for an Expo SDK 57 application (see the release status above). Keep using @next in the commands below.

Prerequisites: Node.js and npm/npx available locally, and the tested version table if you already have an Expo project instead of creating a new one below. Already on 0.86.2-rc.1? Read Upgrading from 0.86.2-rc.1 first — the required Sheet provider rewiring is a breaking native-root change.

create-expo-app’s current default template is an Expo Router app whose root is app/_layout.tsx. That layout is where the Provider section below mounts BeeUIProvider.

Terminal window
npx create-expo-app@latest my-app
cd my-app

Install BeeUI and every runtime peer it declares in one command — installing @beemvp/beeui-ui alone first and the React Native peers afterward is the ordering that produces an ERESOLVE on react-native:

Terminal window
npm install @beemvp/beeui-ui@next @beemvp/beeui-core@next @beemvp/beeui-tokens@next \
react@19.2.3 react-dom@19.2.3 react-native@0.86.2 \
react-native-web@0.21.0 react-native-safe-area-context@~5.7.0 \
react-native-teleport@~1.1.13 tailwindcss@4.3.3 uniwind@1.10.1

Pin @0.86.2-rc.3 instead of @next when you need an immutable RC dependency.

Optional native peers used by Sheet, DatePicker and DateTimePicker are listed in Compatibility; install only the ones your application uses.

Create global.css at the project root — the same path the Metro configuration below points cssEntryFile at — and import it once from your root layout (app/_layout.tsx). @source paths are resolved relative to this file:

@import 'tailwindcss';
@import 'uniwind';
@import '@beemvp/beeui-tokens/theme.css';
@source './node_modules/@beemvp/beeui-core/src';
@source './node_modules/@beemvp/beeui-ui/src';

The @source entries are required so Tailwind sees BeeUI’s own source-emitted classes; without them the app builds with no error and renders unstyled.

The maintained Expo fixture uses Uniwind’s Metro wrapper. Copy the whole file, including the require/module.exports wrapper — a bare options object with no wrapper is not valid Metro config:

metro.config.js
const { getDefaultConfig } = require('expo/metro-config');
const { withUniwindConfig } = require('uniwind/metro');
module.exports = withUniwindConfig(getDefaultConfig(__dirname), {
cssEntryFile: './global.css',
dtsFile: './uniwind-types.d.ts',
extraThemes: ['violet-light', 'violet-dark', 'high-contrast-light', 'high-contrast-dark'],
});

Generate Uniwind’s TypeScript artifacts before typechecking

Section titled “Generate Uniwind’s TypeScript artifacts before typechecking”

Metro only writes uniwind-types.d.ts (the file dtsFile above names) the first time it bundles. A fresh checkout that runs tsc --noEmit before ever starting Metro fails with dozens of Property 'className' does not exist on type 'ViewProps' errors — this is not a BeeUI defect, it is Uniwind’s codegen not having run yet. Run its CLI once, and wire it into postinstall so CI and editors never hit this:

Terminal window
npx uniwind generate-artifacts --css ./global.css \
--theme violet-light --theme violet-dark \
--theme high-contrast-light --theme high-contrast-dark \
--dts ./uniwind-types.d.ts
{
"scripts": {
"postinstall": "uniwind generate-artifacts --css ./global.css --theme violet-light --theme violet-dark --theme high-contrast-light --theme high-contrast-dark --dts ./uniwind-types.d.ts",
"typecheck": "tsc --noEmit"
}
}

List the same --theme values passed to extraThemes above so the generated types match the Metro config exactly.

Mount BeeUIProvider in the Expo Router root layout, above the Stack — not inside a bare App() component, which is not what create-expo-app’s current template scaffolds:

app/_layout.tsx
import '../global.css';
import { BeeUIProvider, SafeArea, Screen } from '@beemvp/beeui-ui';
import { Stack } from 'expo-router';
export default function RootLayout() {
return (
<BeeUIProvider>
<Screen>
<SafeArea edges={['top', 'left', 'right']} className="flex-1">
<Stack screenOptions={{ headerShown: false }} />
</SafeArea>
</Screen>
</BeeUIProvider>
);
}

If your project does not use Expo Router, mount the same provider in a bare App() instead:

import { BeeUIProvider, SafeArea, Screen, Text } from '@beemvp/beeui-ui';
export default function App() {
return (
<BeeUIProvider>
<Screen>
<SafeArea edges={['top', 'bottom', 'left', 'right']}>
<Text>BeeUI on Expo</Text>
</SafeArea>
</Screen>
</BeeUIProvider>
);
}

If your app renders Sheet on iOS/Android, mount BeeUI’s public SheetProvider directly below BeeUIProvider, above the rest of the app:

import { BeeUIProvider, SheetProvider } from '@beemvp/beeui-ui';
export default function RootLayout() {
return (
<BeeUIProvider>
<SheetProvider>
{/* Screen / SafeArea / Stack as above */}
</SheetProvider>
</BeeUIProvider>
);
}

SheetProvider is required on native: it installs GestureHandlerRootView and @gorhom/bottom-sheet’s BottomSheetModalProvider itself, so gorhom’s portal host is constructed below BeeUI’s runtime contexts. Do not also mount an outer GestureHandlerRootView/BottomSheetModalProvider — if you followed an earlier BeeUI release’s guidance to wire GestureHandlerRootView > BottomSheetModalProvider > BeeUIProvider, remove that outer BottomSheetModalProvider (and move GestureHandlerRootView inside SheetProvider’s ownership) when you upgrade. SheetProvider deliberately does not reuse an already-present outer gorhom provider.

SheetContent only sees React contexts mounted above SheetProvider. Keep app-wide providers (a query client, i18n, navigation, your own app stores) above SheetProvider; a screen-scoped provider that a Sheet’s content still needs to read must be passed through SheetContent’s bridgeContexts prop instead. Web and the RN Modal fallback do not need SheetProvider mounted for correctness — it is a pass-through there — but it is safe to mount everywhere for one consistent tree. See the Sheet component reference for the full provider and bridgeContexts contract.

The executable authority is examples/expo-package-consumer. From that directory, the release-equivalent flow is:

Terminal window
bash setup.sh
bash bundle.sh

The setup script creates the isolated consumer boundary and the bundle script exercises Metro/export behavior with the fixture’s exact dependency/configuration contract.

For an interactive session:

Terminal window
npx expo start

Use Expo’s normal i, a or w targets when the matching simulator, emulator or browser is available.

A successful Expo export proves Metro resolution for Android, iOS and Web. It is not a substitute for native compilation or simulator/device interaction. BeeUI tracks those evidence classes separately in Release & security and the repository release contract.