---
title: Installation: Expo
description: "Add the native package to an Expo project and render the same components on a device and in a browser."
type: installation
section: Installation
order: 4
---

The components are a separate implementation from the web ones and the same components to
write: the names, the props and the colour roles are shared, and the platform decides how
each one renders.

`@asheeui/native` lives in the AsheeUI repository and is not on npm yet, so an
application takes it from there. Everything after that step works the way the web
installations do.

The `apps/expo-playground` package in the repository is this page, built: it renders every
component in every state, runs on a device and exports to the web.

---

## Before you start

| Requirement | Version | Why |
| --- | --- | --- |
| Expo | SDK 57 | The version the native package is built against |
| React Native | 0.86.3 | The version SDK 57 pairs with |
| NativeWind | 4.2 | The classes a native component states |
| Tailwind CSS | 3.4 | NativeWind 4 compiles the v3 directives |
| Node.js | 18 or newer | The CLI and the bundler |

A version the package was not built against is the first thing to check when a component
renders without its styles.

---

## Create a project

If you're starting fresh, scaffold an Expo app:

<PackageManagerTabs type="create" framework="expo" />

If you have an existing Expo project, skip to the next step.

---

## Get the packages

The native package is not published, so it is taken from the repository. The packages
inside the repository depend on each other with `workspace:*`, which resolves inside the
workspace a package belongs to, so an application joins the workspace rather than
installing a copy of it.

**`package.json`**

```json
{
  "dependencies": {
    "@asheeui/native": "workspace:*",
    "@asheeui/shared": "workspace:*"
  }
}
```

Clone the repository, add your application as a package in it, and install from the
workspace root. Nothing is copied into your project in the process: the package ships its
source, and the bundler compiles it.

---

## Set up NativeWind

Install the styling layer:

<PackageManagerTabs type="install" package="nativewind@4.2.7 react-native-css-interop@0.2.7" />

Then point the bundler, Babel and Tailwind at each other.

**`metro.config.js`**

```jsx
const { getDefaultConfig } = require("expo/metro-config");
const { withNativeWind } = require("nativewind/metro");

const config = getDefaultConfig(__dirname);

module.exports = withNativeWind(config, { input: "./global.css" });
```

**`babel.config.js`**

```jsx
module.exports = (api) => {
  api.cache(true);
  return {
    presets: [
      ["babel-preset-expo", { jsxImportSource: "nativewind" }],
      "nativewind/babel",
    ],
  };
};
```

**`tailwind.config.js`**

```jsx
module.exports = {
  content: ["./index.js", "./src/**/*.{ts,tsx}"],
  presets: [require("nativewind/preset")],
  theme: {
    extend: {
      colors: {
        background: "#FFFFFF",
        foreground: "#111827",
        primary: "#EA580C",
        secondary: "#F1F5F9",
        danger: "#DC2626",
        warning: "#D97706",
        success: "#16A34A",
      },
    },
  },
};
```

**`global.css`**

```css
@tailwind base;
@tailwind components;
@tailwind utilities;
```

The application imports that file once, at its entry point:

**`index.js`**

```jsx
import { registerRootComponent } from "expo";

import App from "./src/App";
import "./global.css";

registerRootComponent(App);
```

A component gains a `className` from NativeWind, so the platform's types are declared
once:

**`nativewind-env.d.ts`**

```ts
/// <reference types="nativewind/types" />
```

Those seven values under `colors` are the theme's roles rather than a palette invented for
one screen: a component states `bg-primary` and `text-foreground`, and what those resolve
to is the line above. Change them, and the device, the simulator and the browser change
together, which is the whole of writing the theme once.

---

## Wrap your application

The provider is mounted once, above everything the application renders, and it holds the
defaults a component resolves against:

**`src/App.tsx`**

```tsx
import { AsheeNativeProvider } from "@asheeui/native";

export default function App() {
  return (
    <AsheeNativeProvider config={{ defaultRadius: "lg" }}>
      <Screen />
    </AsheeNativeProvider>
  );
}
```

---

## Verify it works

Render a few components:

```tsx
import { Badge, Button, Card, Text, VStack } from "@asheeui/native";

export function Screen() {
  return (
    <VStack gap="lg">
      <Text role="heading-lg">Hello AsheeUI</Text>
      <Card title="Inbox" description="12 unread">
        <VStack gap="sm">
          <Badge color="primary">New</Badge>
          <Button onPress={() => {}}>Open</Button>
        </VStack>
      </Card>
    </VStack>
  );
}
```

You should see the heading in the theme's typography, a surface with a border and a
radius, and a control in the accent colour. If the components render without their styles,
the Tailwind content paths and the Metro plugin are the first two places to look.

To run the same application in a browser, set the web bundler in the Expo configuration
and export it:

**`app.json`**

```json
{
  "expo": {
    "web": {
      "bundler": "metro",
      "output": "single"
    }
  }
}
```

```bash
npx expo export --platform web
```

---

## Where native differs from web

The vocabulary is shared, and a few things are stated the way the platform states them
rather than the way a browser does. They are worth knowing before reading the component
reference, which documents the web surface.

- **A disabled control is `isDisabled`.** A native `Button` states the disabled state as
  `isDisabled`, where the web `Button` states `disabled`. Both are the same option, spelled
  the way each platform spells it.
- **Direction is a prop, not a breakpoint class.** `<Stack direction="row">` lays its
  children out in a row on every screen. A screen that changes direction at a width asks
  `useBreakpoint` and passes the answer, because the platform reports the window as a value
  rather than as a set of class prefixes.
- **A class is NativeWind's, not Tailwind's.** A `className` on a platform view is a string
  of classes that the renderer understands or ignores. It is not Tailwind in a browser, so a
  rule that depends on the cascade behaves differently, and a component's own props are the
  part that is guaranteed.
- **The package is not published.** The web package is on npm; the native one is taken from
  the repository, as above, and will be published when its remaining components match the
  web contracts.

---

## Next steps

- [Getting Started](/docs/getting-started): the CLI, the provider and a first component on
  the web.
- [Components](/docs/components): the component reference, whose vocabulary the native
  package shares, with the differences listed above.
- [Theming](/docs/theming): what each colour role means and how one theme is declared once
  for every platform.
- [The Expo playground](https://expo-playground-beige.vercel.app): this page as a working
  application, including the layout kit. It builds for a device and exports to the web.

