React Native
Set up beUI Native
beUI Native components are plain source you copy into your app, like the web components. Do the setup below once per app. After that, adding a component is copying its files from its page. It works in Expo and in bare React Native.
Requirements
The components are built and tested against these versions.
- Expo SDK, React Native57, 0.86 with React 19
- Reanimated and Worklets4.5 and 0.10
- Gesture Handler2.32
- Uniwind with Tailwind CSS1.12 with 4.3
1. Install the libraries
In an Expo app, expo install picks the versions that match your SDK. Uniwind and Tailwind come from npm.
npx expo install react-native-reanimated react-native-worklets react-native-gesture-handler
npm install uniwind tailwindcssExpo's Babel preset already includes the Worklets plugin that Reanimated 4 needs. In a bare React Native app, add it to babel.config.js yourself:
module.exports = {
presets: ["module:@react-native/babel-preset"],
// Reanimated 4 runs animations as worklets. Keep this plugin last.
plugins: ["react-native-worklets/plugin"],
};2. Set up Uniwind
Components are styled with Tailwind class names through Uniwind. Create global.css in the project root. It loads Tailwind, Uniwind and the beUI theme you add in the next step.
@import "tailwindcss";
@import "uniwind";
@import "./src/styles/theme.css";Then point Metro at it. withUniwindConfig must be the outermost wrapper if you use other Metro wrappers, and cssEntryFile must be a relative path.
const { getDefaultConfig } = require("expo/metro-config");
const { withUniwindConfig } = require("uniwind/metro");
const config = getDefaultConfig(__dirname);
// Uniwind must be the outermost wrapper.
module.exports = withUniwindConfig(config, {
cssEntryFile: "./global.css",
dtsFile: "./src/uniwind-types.d.ts",
});In a bare React Native app, the default config comes from @react-native/metro-config instead:
const { getDefaultConfig } = require("@react-native/metro-config");
const { withUniwindConfig } = require("uniwind/metro");
const config = getDefaultConfig(__dirname);
// Uniwind must be the outermost wrapper.
module.exports = withUniwindConfig(config, {
cssEntryFile: "./global.css",
dtsFile: "./src/uniwind-types.d.ts",
});Uniwind writes the class name types to dtsFile when Metro starts, so start it once before checking types. If TypeScript then can't find the global.css import, declare CSS files:
declare module "*.css";3. Add the theme tokens
Components use semantic colors like bg-primary and text-muted, defined for light and dark mode, so they follow the system appearance. Save this file as src/styles/theme.css, the path global.css imports, and change the values to match your brand.
/* Matches the default beUI palette in ui-components/lib/themes.ts.
Native surface maps to card; muted maps to muted-foreground. */
@layer theme {
:root {
@variant light {
--color-background: oklch(99% 0 0);
--color-surface: oklch(97% 0 0);
--color-foreground: oklch(15% 0 0);
--color-muted: oklch(50% 0 0);
--color-border: oklch(15% 0 0 / 0.06);
--color-border-strong: oklch(15% 0 0 / 0.16);
--color-primary: #0285f7;
--color-on-primary: #ffffff;
--color-destructive: #dc2626;
--color-success: #059669;
--color-warning: #d97706;
}
}
:root {
@variant dark {
--color-background: #151515;
--color-surface: #1c1c1c;
--color-foreground: oklch(96% 0 0);
--color-muted: oklch(62% 0 0);
--color-border: rgb(255 255 255 / 0.05);
--color-border-strong: rgb(255 255 255 / 0.14);
--color-primary: #0285f7;
--color-on-primary: #ffffff;
--color-destructive: #f87171;
--color-success: #34d399;
--color-warning: #fbbf24;
}
}
}
4. Wrap your app
Import global.css from your root component, not from the file that registers the app, or every style change reloads the whole app. Wrap the app in GestureHandlerRootView so swipeable and draggable components can receive gestures. With Expo Router, both go in the root layout:
import "../global.css";
import { Stack } from "expo-router";
import { GestureHandlerRootView } from "react-native-gesture-handler";
export default function RootLayout() {
return (
<GestureHandlerRootView style={{ flex: 1 }}>
<Stack />
</GestureHandlerRootView>
);
}In a bare app, do the same in App.tsx. For forms, also wrap the app in KeyboardProvider from react-native-keyboard-controller to keep focused fields above the keyboard.
5. Add a component
Run the beUI Native CLI from your app's root. It copies the component and the shared files it needs into src/ and installs any libraries you don't have yet.
npx beui-native add tabsTo copy files by hand instead, open the component from the React Native components and use its Manual steps. Either way, keep the folder layout, since components import shared files by relative path. Tabs ends up like this:
global.css
metro.config.js
src/
components/
tabs/
index.ts
tabs.tsx
hooks/
use-reduced-motion.ts
primitives/
render-icon.tsx
styles/
motion.ts
theme.cssThen use it like any other component:
import { Text } from "react-native";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "../src/components/tabs";
export default function Screen() {
return (
<Tabs defaultValue="overview" className="p-4">
<TabsList>
<TabsTrigger value="overview">Overview</TabsTrigger>
<TabsTrigger value="activity">Activity</TabsTrigger>
</TabsList>
<TabsContent value="overview">
<Text className="text-foreground">High-level summary.</Text>
</TabsContent>
<TabsContent value="activity">
<Text className="text-foreground">Recent events.</Text>
</TabsContent>
</Tabs>
);
}Tailwind finds class names in files under the folder that holds global.css. If it lives somewhere else, like app/, add an @source for the components:
@import "tailwindcss";
@import "uniwind";
@import "../src/styles/theme.css";
/* This file is in app/, so the components in src/ need an explicit source. */
@source "../src";Try before you copy
Every React Native component page has a QR code that opens it in Expo Go (SDK 57), so you can feel the motion on your phone first. Coding agents can fetch any component with its install commands and files from https://beui.dev/native/r, or through the beUI MCP server. See the Agent Guide.
Every component follows the system Reduce Motion setting and takes a disableAnimation prop. The source is on GitHub.