AZMARA PLATFORM
Guide

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.

View as Markdown for coding assistants, or see llms.txt.

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

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.

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
{
  "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
@import "tailwindcss";
@import "@azmr/ui/styles.css";

The page links the built stylesheet and the bundle:

index.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:

src/main.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
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
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.

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

On this page