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.
View as Markdown for coding assistants, or see llms.txt.
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
pnpm add @azmr/ui @azmr/core
# or
npm install @azmr/ui @azmr/corereact, react-dom and react-router are already in your app. @azmr/core is a peer dependency, so install it yourself: see Installation.
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:
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:
@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:
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:
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():
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
darkcookie rendersdata-theme="dark"on<html>, and no cookie renders no attribute.
Next steps
- Theming: change the brand colour, density and dark mode.
- Components: every component, with live demos.
- Examples: complete screens to copy.
- Tailwind CSS setup: what the package needs from Tailwind.