Install in Next.js
Add @azmr/ui to a Next.js App Router app with Tailwind CSS v4, a first component and dark mode, and see which parts are Server Components.
View as Markdown for coding assistants, or see llms.txt.
This guide adds @azmr/ui to a Next.js app that uses the App Router. It takes about five minutes. If you do not have an app yet, create one with npx create-next-app@latest and answer yes to TypeScript, Tailwind CSS and the App Router; everything below is what you add or change.
1. Install
pnpm add @azmr/ui @azmr/core
# or
npm install @azmr/ui @azmr/corenext, react and react-dom are already in your app. @azmr/core is a peer dependency, so install it yourself: see Installation.
2. Set up Tailwind CSS v4
Tailwind v4 runs through PostCSS in Next.js. Install it if create-next-app did not (pnpm add -D tailwindcss @tailwindcss/postcss postcss), then use its plugin:
export default {
plugins: { "@tailwindcss/postcss": {} },
};3. Import the stylesheet
In your global CSS, import Tailwind and then the package's stylesheet:
@import "tailwindcss";
@import "@azmr/ui/styles.css";Then load that file once, in the root layout. The layout below also sets up dark mode, which is explained in step 5:
import { ThemeProvider } from "@azmr/ui/react";
import {
DENSITY_COOKIE,
htmlAttributes,
parseDensity,
parseThemePreference,
THEME_COOKIE,
} from "@azmr/ui/theme";
import { cookies } from "next/headers";
import type { ReactNode } from "react";
import "./globals.css";
export default async function RootLayout({ children }: { children: ReactNode }) {
// Read the saved choice on the server, so the first paint is already in the right mode.
const jar = await cookies();
const theme = parseThemePreference(jar.get(THEME_COOKIE)?.value);
const density = parseDensity(jar.get(DENSITY_COOKIE)?.value);
return (
<html lang="en-NZ" {...htmlAttributes({ theme, density })}>
<body>
<ThemeProvider defaultTheme={theme} defaultDensity={density}>
{children}
</ThemeProvider>
</body>
</html>
);
}4. Use a component
A page in the App Router is a Server Component, and it can render @azmr/ui components directly:
import { Button } from "@azmr/ui/button";
import { Input } from "@azmr/ui/input";
import { ThemeToggle } from "./theme-toggle";
// A Server Component. Button and Input are client components, and a Server Component may render
// them: the package marks its own client entries with "use client".
export default function Page() {
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.
Server Components and client components
Button and Input handle clicks and focus, so they are client components. You do not need to mark your own file "use client" to use them: the package marks its client entries itself, so a Server Component may import and render them. Some entries, such as Spinner, Icon and the layout components, have no client code at all and render on the server.
One rule matters: a Server Component cannot pass a function to a client component. An onClick on the Button above would make the page fail when it renders (Event handlers cannot be passed to Client Component props), and TypeScript does not warn you. Move the interactive part into a small component marked "use client", as ThemeToggle is below. The Server Components guide lists which entries are which, with the rule in full.
5. Add dark mode
With no setup at all the page already follows the operating system. To let people choose, the layout above reads a saved choice from a cookie on the server and renders it onto <html>, so the first paint is already in the right mode. ThemeProvider keeps the choice and writes the cookie. A client component changes it with useTheme():
"use client";
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 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.