# Install in plain React

> Add @azmr/ui to a React app with no framework, using Tailwind CSS v4's command line tool and esbuild, a first component and dark mode.

Source: https://platform.azmara.io/docs/guide/install-react

This guide adds `@azmr/ui` to a React app with no framework or dev server: Tailwind's command line tool builds the CSS and esbuild builds the JavaScript. It is the smallest setup that works, and the same three steps apply to any bundler (webpack, Parcel, Rspack): run Tailwind v4, import the stylesheet, import the components. If you use Vite, Next.js or React Router, use their guides instead.

## 1. Install

```bash
pnpm add @azmr/ui @azmr/core react react-dom
pnpm add -D tailwindcss @tailwindcss/cli esbuild typescript @types/react @types/react-dom
# or
npm install @azmr/ui @azmr/core react react-dom
npm install -D tailwindcss @tailwindcss/cli esbuild typescript @types/react @types/react-dom
```

`@azmr/core` is a peer dependency, so install it yourself: see [Installation](https://platform.azmara.io/docs/guide/installation.md#the-four-steps).

pnpm 10 and newer does not run a package's install script until you approve it, and in CI it reports an unapproved one as an error. Two of these packages have install scripts, `esbuild` and `@parcel/watcher` (used by the Tailwind CLI). Run `pnpm approve-builds` and approve them; the check for this guide approves the same two. npm and Yarn run install scripts without asking.

## 2. Set up Tailwind CSS v4

Add two scripts that build the stylesheet and the bundle:

`package.json`

```json
{
  "name": "azmr-ui-guide-esbuild",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "description": "The plain React getting-started guide (Tailwind CLI and esbuild, no framework), built against the PACKED @azmr/ui. The guide quotes these files, so they cannot drift. See ../../../scripts/verify-guides.mjs.",
  "scripts": {
    "build:css": "tailwindcss --input src/app.css --output dist/app.css",
    "build:js": "esbuild src/main.tsx --bundle --minify --jsx=automatic --outfile=dist/main.js",
    "build": "npm run build:css && npm run build:js",
    "typecheck": "tsc --noEmit"
  },
  "dependencies": {
    "@azmr/core": "*",
    "@azmr/ui": "*",
    "react": "^19.3.0",
    "react-dom": "^19.3.0"
  },
  "devDependencies": {
    "@tailwindcss/cli": "^4.3.3",
    "@types/react": "^19.3.0",
    "@types/react-dom": "^19.3.0",
    "esbuild": "^0.28.2",
    "tailwindcss": "^4.3.3",
    "typescript": "^5.7.3"
  }
}
```

If your bundler has a Tailwind v4 plugin or PostCSS support, use that instead of `@tailwindcss/cli`: the stylesheet below is the same.

## 3. Import the stylesheet

In your global CSS, import Tailwind and then the package's stylesheet:

`src/app.css`

```css
@import "tailwindcss";
@import "@azmr/ui/styles.css";
```

The page links the built stylesheet and the bundle:

`index.html`

```html
<!doctype html>
<html lang="en-NZ">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>@azmr/ui with plain React</title>
    <link rel="stylesheet" href="/dist/app.css" />
  </head>
  <body>
    <div id="root"></div>
    <script src="/dist/main.js"></script>
  </body>
</html>
```

## 4. Use a component

The entry file starts the app, and also wraps it in `ThemeProvider` for [step 5](#5-add-dark-mode):

`src/main.tsx`

```tsx
import { ThemeProvider } from "@azmr/ui/react";
import {
  DENSITY_COOKIE,
  parseDensity,
  parseThemePreference,
  readCookie,
  THEME_COOKIE,
} from "@azmr/ui/theme";
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import App from "./App";

// Read the choice saved by an earlier visit, so a reload keeps it.
const theme = parseThemePreference(readCookie(document.cookie, THEME_COOKIE));
const density = parseDensity(readCookie(document.cookie, DENSITY_COOKIE));

createRoot(document.getElementById("root") as HTMLElement).render(
  <StrictMode>
    <ThemeProvider defaultTheme={theme} defaultDensity={density}>
      <App />
    </ThemeProvider>
  </StrictMode>,
);
```

`src/App.tsx`

```tsx
import { Button } from "@azmr/ui/button";
import { Input } from "@azmr/ui/input";
import { ThemeToggle } from "./theme-toggle";

export default function App() {
  return (
    <main className="mx-auto flex max-w-sm flex-col gap-3 p-6">
      <h1 className="text-2xl font-semibold">Hello</h1>
      <label htmlFor="name" className="text-sm font-medium">
        Your name
      </label>
      <Input id="name" placeholder="Aroha" />
      <Button>Say hello</Button>
      <ThemeToggle />
    </main>
  );
}
```

Run `pnpm build`, serve the folder with any static file server, and open `index.html`. You should see a heading, a labelled field and a button, styled with the design tokens.

## 5. Add dark mode

With no setup at all the page already follows the operating system. To let people choose, the entry file above wraps the app in `ThemeProvider`, which remembers a choice in a cookie. The entry file reads that cookie when the page starts, so a reload keeps the choice. A component changes it with `useTheme()`:

`src/theme-toggle.tsx`

```tsx
import { Button } from "@azmr/ui/button";
import { useTheme } from "@azmr/ui/react";

export function ThemeToggle() {
  const { resolvedTheme, setTheme } = useTheme();
  const next = resolvedTheme === "dark" ? "light" : "dark";
  return (
    <Button variant="outline" onClick={() => setTheme(next)}>
      Switch to {next} mode
    </Button>
  );
}
```

Density works the same way: `useTheme()` also returns `density` and `setDensity`, with the values `compact`, `comfortable` and `spacious`.

A page that is built in the browser can only apply the saved choice once its JavaScript has run, so someone who chose dark while their system is light may see a brief flash of the light theme first. If that matters, inline the `themeInitScript()` described in [Theming](https://platform.azmara.io/docs/guide/theming.md#remember-the-choice-without-a-flash).

## What was tested

The files above are the files of an app that CI builds. On every change it packs `@azmr/ui` into a tarball, installs it into that app, type-checks and builds it, and checks that:

- Tailwind found the package's classes inside `node_modules`, with nothing added to the config;
- the colour tokens are in the built stylesheet;
- the built bundle contains the app and the Button, and reads the saved theme cookie.

Bundlers other than esbuild are not built in CI. The package ships standard ES modules and a plain stylesheet, so nothing here is specific to esbuild, but only the setups with a guide are tested.

## Next steps

- [Theming](https://platform.azmara.io/docs/guide/theming.md): change the brand colour, density and dark mode.
- [Components](https://platform.azmara.io/docs/components.md): every component, with live demos.
- [Examples](https://platform.azmara.io/docs/examples.md): complete screens to copy.
- [Tailwind CSS setup](https://platform.azmara.io/docs/guide/tailwind.md): what the package needs from Tailwind.
