OneApp Docs
PackagesUI

@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-ui

One 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 styles

Uses 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-ui

2. Configure NativeWind (React Native apps)

babel.config.js
module.exports = {
  presets: ["babel-preset-expo"],
  plugins: ["nativewind/babel"]
};
tailwind.config.ts
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

app/profile/page.tsx
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

app/components/AnimatedCard.tsx
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-ui

Build configuration: Uses tsdown with createDistConfig('client', ...) for distribution builds.


Technical Details

For Developers: Technical implementation details

Overview

PropertyValue
Locationpackages/uni-ui
Dependenciesreact-native, nativewind, react-native-reanimated
PlatformsWeb, iOS, Android

Export Paths

PathDescription
@repo/uni-uiAll universal components
@repo/uni-ui/primitivesBase primitives
@repo/uni-ui/animationsAnimated 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-end

Badge

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-end

Animations

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-end

Platform-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-end

Configuration

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"]
};

On this page