@repo/uni-ui
Cross-platform UI components for web and React Native. Write once, run on iOS, Android, and web. Same Button, Input, Card works everywhere. Use Tailwind classes with NativeWind for consistent styling.
Quick Start
Build cross-platform UIs in 10 minutes:
pnpm add @repo/uni-uiOne component, three platforms. Tailwind styling everywhere. Skip to Quick Start →
Why @repo/uni-ui?
Building separate UIs for web and mobile doubles development time. Platform-specific styling (React Native StyleSheet vs CSS) creates inconsistencies. Sharing components between platforms requires complex abstractions. Mobile apps look different from web versions. Designers create mockups once, developers build twice.
@repo/uni-ui solves this with components that work natively on all platforms using NativeWind for consistent Tailwind styling.
Production-ready with NativeWind integration, Reanimated animations, dark mode support, and platform-specific optimizations.
Use cases
- Mobile + Web apps — Build one codebase that runs natively on iOS, Android, and web
- Design systems — Consistent UI across all platforms with shared Tailwind tokens
- MVPs — Ship faster with components that work everywhere
- Hybrid teams — Web developers can build mobile UIs using familiar Tailwind syntax
- Marketing sites — Reuse mobile app components in Next.js landing pages
How it works
@repo/uni-ui provides components that render natively on each platform:
import { Button, Card, VStack, Text } from "@repo/uni-ui";
// Works on web (Next.js), iOS, and Android
<Card className="shadow-md">
<VStack space={4} className="p-4">
<Text className="text-lg font-bold">Cross-Platform Card</Text>
<Button variant="primary" onPress={() => alert("Works everywhere!")}>
Click Me
</Button>
</VStack>
</Card>
// Web: Renders as <div> with CSS
// Native: Renders as <View> with native stylesUses React Native primitives (View, Text, Pressable) that compile to native on iOS/Android and DOM on web, styled with NativeWind (Tailwind for React Native).
Key features
Cross-platform — Button, Input, Card, Text work on web, iOS, and Android
Tailwind styling — Use className with Tailwind classes via NativeWind
Animations — Reanimated-powered animations that work on all platforms
Dark mode — Automatic dark mode with dark: classes
Type-safe — Full TypeScript support with platform-specific types
Layout components — VStack, HStack with automatic spacing
Quick Start
1. Install the package
pnpm add @repo/uni-ui2. Configure NativeWind (React Native apps)
module.exports = {
presets: ["babel-preset-expo"],
plugins: ["nativewind/babel"]
};import uniUIPreset from "@repo/uni-ui/tailwind-preset";
export default {
presets: [uniUIPreset],
content: ["./src/**/*.{ts,tsx}", "../../packages/uni-ui/src/**/*.{ts,tsx}"]
};3. Build a cross-platform screen
import { VStack, Card, Text, Button, Avatar } from "@repo/uni-ui";
export default function ProfileScreen() {
return (
<VStack space={4} className="p-4">
<Card className="shadow-md">
<VStack space={3} className="p-4 items-center">
<Avatar src="https://example.com/avatar.jpg" size="lg" />
<Text className="text-xl font-bold">John Doe</Text>
<Text className="text-gray-600">john@example.com</Text>
</VStack>
</Card>
<Button variant="primary" onPress={() => console.log("Edit")}>
Edit Profile
</Button>
</VStack>
);
}4. Add animations
import { FadeIn, AnimatedPressable } from "@repo/uni-ui/animations";
import { Card, Text } from "@repo/uni-ui";
export function AnimatedCard() {
return (
<FadeIn delay={200}>
<AnimatedPressable pressAnimation="scale">
<Card className="p-4">
<Text>Press me for animation</Text>
</Card>
</AnimatedPressable>
</FadeIn>
);
}That's it! Your components now work on web, iOS, and Android with consistent Tailwind styling.
Platform-specific styles
Use Platform.select for platform-specific styling:
import { Platform } from "react-native";
<Button
className={Platform.select({
ios: "rounded-full",
android: "rounded-md",
web: "rounded-lg",
})}
>
Platform Specific
</Button>Distribution
This package is available as @oneapp/uni-ui for use outside the monorepo.
npm install @oneapp/uni-uiBuild configuration: Uses tsdown with
createDistConfig('client', ...) for distribution builds.
Technical Details
For Developers: Technical implementation details
Overview
| Property | Value |
|---|---|
| Location | packages/uni-ui |
| Dependencies | react-native, nativewind, react-native-reanimated |
| Platforms | Web, iOS, Android |
Export Paths
| Path | Description |
|---|---|
@repo/uni-ui | All universal components |
@repo/uni-ui/primitives | Base primitives |
@repo/uni-ui/animations | Animated components |
Universal Components
Button
import { Button } from "@repo/uni-ui";
// Works on web and native
// highlight-start
<Button variant="primary" onPress={() => console.log("Pressed")}>
Click Me
</Button>
// highlight-end
// With loading state
<Button loading disabled>
Submitting...
</Button>Text
import { Text, Heading } from "@repo/uni-ui";
// highlight-start
<Heading level={1}>Main Title</Heading>
<Heading level={2}>Subtitle</Heading>
// highlight-end
<Text>Regular paragraph text.</Text>
<Text variant="muted">Muted text for secondary info.</Text>View / Box
Layout Components
Use VStack for vertical layouts and HStack for horizontal. The space prop adds consistent spacing between
children.
import { Box, VStack, HStack } from "@repo/uni-ui";
// Flexbox layouts
// highlight-start
<VStack space={4}>
<Box className="p-4 bg-gray-100 rounded-lg">
<Text>Item 1</Text>
</Box>
<Box className="p-4 bg-gray-100 rounded-lg">
<Text>Item 2</Text>
</Box>
</VStack>
// highlight-end
<HStack space={2} className="items-center">
<Avatar src={user.image} />
<Text>{user.name}</Text>
</HStack>Input
import { Input, TextArea } from "@repo/uni-ui";
<Input
placeholder="Enter your email"
// highlight-start
keyboardType="email-address"
autoCapitalize="none"
// highlight-end
/>
<TextArea
placeholder="Write a message..."
numberOfLines={4}
/>Card
import { Card, CardHeader, CardBody, CardFooter } from "@repo/uni-ui";
<Card className="shadow-md">
<CardHeader>
<Heading level={3}>Card Title</Heading>
</CardHeader>
<CardBody>
<Text>Card content goes here.</Text>
</CardBody>
<CardFooter>
<Button size="sm">Action</Button>
</CardFooter>
</Card>Avatar
import { Avatar, AvatarGroup } from "@repo/uni-ui";
// Single avatar
<Avatar
src="https://example.com/avatar.jpg"
alt="User Name"
size="md"
// highlight-next-line
fallback="UN"
/>
// Avatar group
// highlight-start
<AvatarGroup max={3}>
<Avatar src={user1.image} />
<Avatar src={user2.image} />
<Avatar src={user3.image} />
<Avatar src={user4.image} />
</AvatarGroup>
// highlight-endBadge
import { Badge } from "@repo/uni-ui";
// highlight-start
<Badge variant="default">Default</Badge>
<Badge variant="success">Success</Badge>
<Badge variant="warning">Warning</Badge>
<Badge variant="error">Error</Badge>
// highlight-endAnimations
Animated Pressable
import { AnimatedPressable } from "@repo/uni-ui/animations";
<AnimatedPressable
onPress={handlePress}
className="p-4 bg-blue-500 rounded-lg"
// highlight-next-line
pressAnimation="scale" // "scale" | "opacity" | "both"
>
<Text className="text-white">Press Me</Text>
</AnimatedPressable>Fade In
import { FadeIn } from "@repo/uni-ui/animations";
// highlight-next-line
<FadeIn delay={200} duration={300}>
<Card>Content</Card>
</FadeIn>Slide In
import { SlideIn } from "@repo/uni-ui/animations";
// highlight-next-line
<SlideIn direction="up" delay={100}>
<Text>Slides in from bottom</Text>
</SlideIn>NativeWind Styling
Tailwind for Native
NativeWind brings Tailwind CSS to React Native. Write familiar className strings that work on all platforms.
import { View, Text } from "@repo/uni-ui/primitives";
// highlight-start
<View className="flex-1 justify-center items-center bg-white dark:bg-gray-900">
<Text className="text-lg font-bold text-gray-900 dark:text-white">
Hello, World!
</Text>
</View>
// highlight-endPlatform-Specific Code
import { Platform } from "react-native";
import { Button } from "@repo/uni-ui";
<Button
// highlight-start
className={Platform.select({
ios: "rounded-full",
android: "rounded-md",
web: "rounded-lg",
})}
// highlight-end
>
Platform Specific
</Button>Icons
import { Icon } from "@repo/uni-ui";
// Uses @expo/vector-icons on native, Lucide on web
// highlight-start
<Icon name="home" size={24} color="blue" />
<Icon name="settings" size={20} />
<Icon name="user" size={16} className="text-gray-500" />
// highlight-endConfiguration
Tailwind Setup (Web)
// tailwind.config.ts
import uniUIPreset from "@repo/uni-ui/tailwind-preset";
export default {
// highlight-next-line
presets: [uniUIPreset],
content: ["./src/**/*.{ts,tsx}", "../../packages/uni-ui/src/**/*.{ts,tsx}"]
};NativeWind Setup (Native)
// babel.config.js
module.exports = {
presets: ["babel-preset-expo"],
// highlight-next-line
plugins: ["nativewind/babel"]
};Related Packages
- @repo/ui - Web-only React components
- @repo/uni-app - Universal app utilities