CollectUICollectUI

Web + Native Monorepo

Install and use HeroUI Pro component library in a Web + Native monorepo project

This article is manually written (md) and AI-optimized (mdx).

In a Web + Native full-stack monorepo, hpsetup can be run from the root directory. It will automatically detect all Pro packages in the workspaces and install them.

Create a Project

Initialize a monorepo project with Turborepo using create better-t-stack:

pnpm create better-t-stack@latest my-better-t-app \
  --frontend tanstack-router native-uniwind \
  --backend none \
  --runtime none \
  --api none \
  --auth none \
  --payments none \
  --database none \
  --orm none \
  --db-setup none \
  --package-manager pnpm \
  --git \
  --web-deploy none \
  --server-deploy none \
  --install \
  --addons turborepo \
  --examples none

Run hpsetup

Navigate into the project directory and run hpsetup:

hpsetup running in a monorepo

Configure Styles

After installing the Pro dependencies, you need to import the styles in each project's global CSS.

Web Project

packages/ui/src/styles/globals.css
@import "tailwindcss";
@import "@heroui/styles";
@import "@heroui-pro/react/css";

Native Project

apps/native/global.css
@import 'tailwindcss';
@import 'uniwind';
@import 'heroui-native/styles';
@import 'heroui-native-pro/styles';

@source './node_modules/heroui-native/lib';
@source './node_modules/heroui-native-pro/lib';

Add Example Components

Create Pro components in each project's components/ directory.

Web: AreaChart

apps/web/src/components/area-chart-demo.tsx
"use client";

import {Card} from "@heroui/react";

import { ChartTooltip } from "@heroui-pro/react/chart-tooltip";
import {AreaChart} from "@heroui-pro/react/area-chart";

const revenueData = [
  {month: "Jan", revenue: 4200},
  {month: "Feb", revenue: 5800},
  {month: "Mar", revenue: 4900},
  {month: "Apr", revenue: 7200},
  {month: "May", revenue: 6100},
  {month: "Jun", revenue: 8400},
  {month: "Jul", revenue: 7800},
  {month: "Aug", revenue: 9200},
  {month: "Sep", revenue: 8600},
  {month: "Oct", revenue: 10200},
  {month: "Nov", revenue: 9800},
  {month: "Dec", revenue: 11500},
];

export default function AreaChartDemo() {
  return (
    <Card className="w-full max-w-[520px] rounded-2xl">
      <Card.Header>
        <Card.Title className="text-base">Monthly Revenue</Card.Title>
      </Card.Header>
      <Card.Content>
        <AreaChart data={revenueData} height={200}>
          <defs>
            <linearGradient id="revenue-fill" x1="0" x2="0" y1="0" y2="1">
              <stop offset="0%" stopColor="var(--chart-3)" stopOpacity={0.2} />
              <stop offset="100%" stopColor="var(--chart-3)" stopOpacity={0.02} />
            </linearGradient>
          </defs>
          <AreaChart.Grid vertical={false} />
          <AreaChart.XAxis dataKey="month" tickMargin={8} />
          <AreaChart.YAxis tickFormatter={(v: number) => `$${(v / 1000).toFixed(0)}k`} width={40} />
          <AreaChart.Area
            dataKey="revenue"
            dot={false}
            fill="url(#revenue-fill)"
            name="Revenue"
            stroke="var(--chart-3)"
            strokeWidth={2}
            type="monotone"
          />
          <AreaChart.Tooltip
            content={({active, label, payload}) => {
              if (!active || !payload?.length) return null;

              return (
                <ChartTooltip>
                  <ChartTooltip.Header>{label}</ChartTooltip.Header>
                  {payload.map((entry) => (
                    <ChartTooltip.Item key={String(entry.dataKey)}>
                      <ChartTooltip.Indicator color={entry.color ?? entry.stroke} />
                      <ChartTooltip.Label>{entry.name}</ChartTooltip.Label>
                      <ChartTooltip.Value>
                        ${Number(entry.value).toLocaleString()}
                      </ChartTooltip.Value>
                    </ChartTooltip.Item>
                  ))}
                </ChartTooltip>
              );
            }}
          />
        </AreaChart>
      </Card.Content>
    </Card>
  );
}

Note that the import path above uses a subpath (@heroui-pro/react/area-chart) rather than the main entry point (@heroui-pro/react). Importing from the main entry point may trigger dependency resolution errors or bundling issues. See Using Subpath Imports.

Native: Stepper

apps/native/components/stepper-demo.tsx
import { Stepper } from 'heroui-native-pro';
import { View } from 'react-native';

export default function StepperDemo() {
  return (
    <View className="flex-1 justify-center bg-background p-4">
      <Stepper>
        <Stepper.Step>
          <Stepper.Rail />
          <Stepper.Content>
            <Stepper.Title>Account</Stepper.Title>
            <Stepper.Description>Create your account</Stepper.Description>
          </Stepper.Content>
        </Stepper.Step>
        <Stepper.Step>
          <Stepper.Rail />
          <Stepper.Content>
            <Stepper.Title>Profile</Stepper.Title>
            <Stepper.Description>Set up your profile</Stepper.Description>
          </Stepper.Content>
        </Stepper.Step>
      </Stepper>
    </View>
  );
}

Import in Pages

Web

apps/web/src/routes/index.tsx
import { createFileRoute } from "@tanstack/react-router";

import AreaChartDemo from "@/components/area-chart-demo";

export const Route = createFileRoute("/")({
  component: HomeComponent,
});

function HomeComponent() {
  return (
    <div className="container mx-auto max-w-3xl px-4 py-2">
      <div className="grid gap-6">
        <AreaChartDemo />
      </div>
    </div>
  );
}

Native

apps/native/app/(drawer)/index.tsx
import { Text, View } from "react-native";

import { Container } from "@/components/container";
import StepperDemo from "@/components/stepper-demo";

export default function Home() {
  return (
    <Container className="px-4 pb-4">
      <View className="py-6 mb-5">
        <Text className="text-3xl font-semibold text-foreground tracking-tight">
          Better T Stack
        </Text>
        <Text className="text-muted text-sm mt-1">Full-stack TypeScript starter</Text>
      </View>
      <StepperDemo />
    </Container>
  );
}

Preview

Run pnpm run dev to start the project.

Common Issues

Web: Multiple React Instances Error

React is null error

This is a multiple React instances issue caused by Vite's pre-bundling. When @heroui-pro/react internally calls React.useMemo, React is null.

Fix: Add dedupe to your Vite configuration:

vite.config.ts
export default defineConfig({
  resolve: {
    tsconfigPaths: true,
    dedupe: ["react", "react-dom", "react/jsx-runtime"],
  },
  // ...
});

After the fix, it renders correctly:

AreaChart rendering correctly

Web: Component Style Issues

Component style issues

This is related to theme variable conflicts with shadcn. Removing the shadcn theme configuration from global.css resolves the issue:

Normal after removing shadcn theme

You can also have AI remove the styles in shadcn that conflict with HeroUI to mix them, but this may affect the rendering of shadcn components. It is recommended to avoid mixing them whenever possible.

Native: Skia Compatibility

If you encounter compatibility warnings related to @shopify/react-native-skia, you need to install a version compatible with your current Expo SDK:

npx -y expo install @shopify/react-native-skia

After the fix, the preview works correctly:

Native Stepper preview working correctly

How is this guide?

Last updated on

On this page