# Install in React Router

> Add @azmr/ui to a React Router app in framework mode (the successor to Remix) with Tailwind CSS v4, a first component and dark mode.

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

This guide adds `@azmr/ui` to a React Router app in framework mode, which is the successor to Remix and is built with Vite. It takes about five minutes. If you do not have an app yet, create one with `npx create-react-router@latest my-app`; everything below is what you add or change. For a Remix v2 app, upgrade to React Router first.

## 1. Install

```bash
pnpm add @azmr/ui @azmr/core
# or
npm install @azmr/ui @azmr/core
```

`react`, `react-dom` and `react-router` are already in your app. `@azmr/core` is a peer dependency, so install it yourself: see [Installation](https://platform.azmara.io/docs/guide/installation.md#the-four-steps).

## 2. Set up Tailwind CSS v4

The React Router template already uses Tailwind's Vite plugin. If yours does not, install it (`pnpm add -D tailwindcss @tailwindcss/vite`) and add it to the Vite config:

`vite.config.ts`

```ts
import { reactRouter } from "@react-router/dev/vite";
import tailwindcss from "@tailwindcss/vite";
import { defineConfig } from "vite";

export default defineConfig({
  plugins: [tailwindcss(), reactRouter()],
});
```

## 3. Import the stylesheet

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

`app/app.css`

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

Load it once in the root route. The root below also sets up dark mode, explained in [step 5](#5-add-dark-mode):

`app/root.tsx`

```tsx
import { ThemeProvider } from "@azmr/ui/react";
import {
  DENSITY_COOKIE,
  htmlAttributes,
  parseDensity,
  parseThemePreference,
  readCookie,
  THEME_COOKIE,
} from "@azmr/ui/theme";
import { Links, Meta, Outlet, Scripts, ScrollRestoration, useRouteLoaderData } from "react-router";
import type { Route } from "./+types/root";
import "./app.css";

// Read the saved choice on the server, so the first paint is already in the right mode.
export function loader({ request }: Route.LoaderArgs) {
  const cookie = request.headers.get("cookie");
  return {
    theme: parseThemePreference(readCookie(cookie, THEME_COOKIE)),
    density: parseDensity(readCookie(cookie, DENSITY_COOKIE)),
  };
}

export function Layout({ children }: { children: React.ReactNode }) {
  const data = useRouteLoaderData<typeof loader>("root");
  return (
    <html lang="en-NZ" {...htmlAttributes({ theme: data?.theme, density: data?.density })}>
      <head>
        <meta charSet="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <Meta />
        <Links />
      </head>
      <body>
        {children}
        <ScrollRestoration />
        <Scripts />
      </body>
    </html>
  );
}

export default function App({ loaderData }: Route.ComponentProps) {
  return (
    <ThemeProvider defaultTheme={loaderData.theme} defaultDensity={loaderData.density}>
      <Outlet />
    </ThemeProvider>
  );
}
```

## 4. Use a component

A route is an ordinary React component, so use `@azmr/ui` in it directly:

`app/routes/home.tsx`

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

export default function Home() {
  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 dev` and open the page. You should see a heading, a labelled field and a button, styled with the design tokens. The page is rendered on the server and hydrated in the browser, and the components work in both.

## 5. Add dark mode

With no setup at all the page already follows the operating system. To let people choose, the root above reads a saved choice from the cookie in its `loader` and renders it onto `<html>`, so the first paint is already in the right mode. It also wraps the routes in `ThemeProvider`, which remembers a new choice in the cookie. A component changes it with `useTheme()`:

`app/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`.

## 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 (including React Router's generated route types) and builds it, starts the server and checks that:

- the page renders the Button and the label on the server;
- Tailwind found the package's classes inside `node_modules`, with nothing added to the config;
- the colour tokens are in the stylesheet;
- a `dark` cookie renders `data-theme="dark"` on `<html>`, and no cookie renders no attribute.

## 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.
