# Getting Started

**New here?** Follow [Choose your path](#choose-your-path), run the install for your path, add the two CSS imports, import a component. Everything else on this page is detail.

→ **[README](README.md)** · **[COMPONENTS.md](COMPONENTS.md)** · **[SKILL.md](SKILL.md)** · **[Live docs](https://design.tollerud.dev/)**

## Choose your path

| Path | Install | CSS / setup |
|------|---------|-------------|
| **Full design system** (most users) | `@tollerud/ui` + [required peers](#full-tollerud-ui) | `globals.css` + `source.css` |
| **Footer only** | `@tollerud/footer` | Tollerud tokens still required — see [Footer only](#footer-only-minimal) |
| **Tailwind v3 (legacy)** | Same peers as full system | `globals-v3.css` + preset — see [Tailwind v3](#tailwind-v3-legacy) |
| **NoirGlowBackground** | Add `@paper-design/shaders-react` | Optional — all other components work without it |

## Install

### Full Tollerud UI

Copy-paste — installs `@tollerud/ui` and all required peers:

```bash
npm install @tollerud/ui clsx tailwind-merge tailwindcss@4 \
  @radix-ui/react-dialog @radix-ui/react-dropdown-menu @radix-ui/react-progress \
  @radix-ui/react-slot @radix-ui/react-tabs @radix-ui/react-tooltip \
  lucide-react framer-motion sonner
```

Looks long — most apps only do this once. Peers stay in your app so you control versions.

As of **v2.0.0**, Radix, Lucide, Framer Motion, and Sonner are **required peer dependencies**. The design system bundles only `class-variance-authority`.

`@paper-design/shaders-react` is an **optional** peer dependency — only needed if you use `NoirGlowBackground`. All other components work without it.

```bash
npm install @paper-design/shaders-react
```

### Peer dependencies by family

| Family | Packages | Needed for |
|--------|----------|------------|
| **Always** | `react`, `react-dom`, `clsx`, `tailwind-merge`, `tailwindcss` | Every integration |
| **Overlays & navigation** | `@radix-ui/react-dialog`, `@radix-ui/react-dropdown-menu`, `@radix-ui/react-tabs`, `@radix-ui/react-tooltip`, `@radix-ui/react-slot`, `@radix-ui/react-progress` | Dialog, Sheet, Drawer, DropdownMenu, Tabs, Tooltip, Progress, Button `asChild` |
| **Icons** | `lucide-react` | Components with built-in icons |
| **Motion** | `framer-motion` | Animated components (e.g. GlowCard, charts) |
| **Toasts** | `sonner` | `<Toaster />` / toast API — mount once at app root |
| **Optional — glow** | `@paper-design/shaders-react` | `NoirGlowBackground` only |

### Footer only (minimal)

If you only need the branded footer — no Radix, Lucide, Framer Motion, Sonner, or other full Tollerud UI peers:

```bash
npm install @tollerud/footer
```

```tsx
import { Footer } from '@tollerud/footer'

<Footer />
```

`@tollerud/footer` is **self-contained by design** — it bundles `clsx` and `tailwind-merge` so footer-only apps avoid extra peer installs. You still need Tailwind with Tollerud tokens (`@tollerud/ui/globals.css` or equivalent) for `text-tollerud-*` / `bg-tollerud-*` classes to resolve.

Use `@tollerud/ui` (or `@tollerud/ui/footer`) when you need the full component set. Apps using both packages may install duplicate `clsx` / `tailwind-merge` versions — harmless in practice; npm dedupes when ranges align.

### Next.js starter

Copy [`examples/next-starter/`](../examples/next-starter/) from this repo — minimal App Router app with `globals.css`, `source.css`, sample page, `Toaster`, and optional `initButtonGlow()` via `ButtonGlowRoot`.

```bash
cp -R examples/next-starter my-app && cd my-app && npm install && npm run dev
```

---

## Start with an AI agent

Copy a prompt into **Cursor**, **Claude Code**, or any agent that can edit your project. Manual steps above still apply — the prompt encodes them so you do not have to wire everything by hand.

If your agent supports project skills, sync [SKILL.md](SKILL.md) first (or let the prompt do it). SKILL.md is the source of truth for exports, props, and gotchas.

After the agent finishes, run **`npx tollerud-ui-audit`** from your app root and fix any errors before merge. See [Consumer project checklist](#consumer-project-checklist).

### New Next.js project

```text
Set up a new Next.js App Router project with @tollerud/ui (Tollerud User Interface / Tollerud UI).

Requirements:
1. Use Next.js with App Router and Tailwind CSS v4 (PostCSS).
2. Install @tollerud/ui and all required peers:
   npm install @tollerud/ui clsx tailwind-merge tailwindcss@4 @radix-ui/react-dialog @radix-ui/react-dropdown-menu @radix-ui/react-progress @radix-ui/react-slot @radix-ui/react-tabs @radix-ui/react-tooltip lucide-react framer-motion sonner
3. In app/globals.css, import BOTH:
   @import "@tollerud/ui/globals.css";
   @import "@tollerud/ui/source.css";
4. Mount <Toaster /> from @tollerud/ui in the root layout.
5. Build the sample home page with exported layout primitives and screen patterns (PageShell, Section, Stack, Button, etc.) — not hand-built min-h-screen/grid utilities. Follow https://design.tollerud.dev/recipes/ for screen structure.
6. Sync the Tollerud UI skill: fetch https://raw.githubusercontent.com/Tollerud/ui/main/SKILL.md and write it to .claude/skills/tollerud-ui/SKILL.md (or the equivalent skills folder for this agent).
7. Do NOT copy @tollerud/ui component source into components/ui. Import from @tollerud/ui only. Do NOT create a local cn() helper — use import { cn } from '@tollerud/ui'.
8. Style links with <Button asChild><Link … /></Button> or buttonVariants() — never nest <a> inside <button>.
9. Use Tollerud tokens (text-tollerud-yellow, bg-tollerud-noir-950) — never hardcode #FFFF00 or #0A0A0A.
10. When done, run npx tollerud-ui-audit and fix all errors before finishing.

If examples/next-starter exists in the Tollerud/ui repo, use it as the reference layout. Otherwise match its structure: globals.css + source.css, layout with Toaster, component-first sample page.

Tell me what you created and any audit output.
```

### Add to an existing project

```text
Add @tollerud/ui (Tollerud User Interface / Tollerud UI) to this existing project.

Requirements:
1. Install @tollerud/ui and required peers if missing:
   npm install @tollerud/ui clsx tailwind-merge tailwindcss@4 @radix-ui/react-dialog @radix-ui/react-dropdown-menu @radix-ui/react-progress @radix-ui/react-slot @radix-ui/react-tabs @radix-ui/react-tooltip lucide-react framer-motion sonner
2. Ensure the Tailwind entry CSS imports BOTH:
   @import "@tollerud/ui/globals.css";
   @import "@tollerud/ui/source.css";
3. Mount <Toaster /> near the app root if we use sonner toasts.
4. Sync SKILL.md from https://raw.githubusercontent.com/Tollerud/ui/main/SKILL.md into the project skills folder (.claude/skills/tollerud-ui/SKILL.md or equivalent).
5. Replace any vendored components/ui copies with imports from @tollerud/ui. Delete local cn() helpers — use import { cn } from '@tollerud/ui' (or '@tollerud/ui/utils' for tree-shaking).
6. Do NOT copy new component source from the Tollerud UI repo into this app.
7. Use component-first composition: @tollerud/ui exports, then layout/screen patterns, then small Tailwind glue only. Screen starting points: https://design.tollerud.dev/recipes/
8. Fix Button/Link nesting: use asChild or buttonVariants() on links.
9. Replace hardcoded brand hex (#FFFF00, #0A0A0A) with Tollerud tokens.
10. Run npx tollerud-ui-audit from the app package root and fix all errors.

Summarize changes and audit results when done.
```

### Footer only

```text
Add the Tollerud branded footer to this project using @tollerud/footer (footer-only — not the full Tollerud UI package).

Requirements:
1. npm install @tollerud/footer
2. Ensure Tailwind is configured with Tollerud tokens via @import "@tollerud/ui/globals.css" (footer-only apps still need token utilities for text-tollerud-* / bg-tollerud-*).
3. import { Footer } from '@tollerud/footer' and render <Footer /> in the layout.
4. Do not install the full @tollerud/ui peer set unless I ask for more components later.

Tell me what you changed.
```

---

## Tailwind Setup (v4)

**Recommended** — package-owned `@source` (works with npm, pnpm, Yarn workspaces, and Bun):

```css
/* app/globals.css */
@import "@tollerud/ui/globals.css";
@import "@tollerud/ui/source.css";
```

`globals.css` bundles Tailwind v4, design tokens, and all component layer styles. `source.css` points Tailwind at the installed package `dist` folder so component utility classes are not purged — no fragile `../node_modules` paths.

### Manual `@source` path (monorepos / custom layouts)

If you prefer an explicit path, resolve it **relative to your CSS file** to `node_modules/@tollerud/ui/dist`:

| Layout | Example CSS file | `@source` path |
|--------|------------------|----------------|
| Next.js App Router (default) | `app/globals.css` | `../node_modules/@tollerud/ui/dist` |
| `src/` app dir | `src/app/globals.css` | `../../node_modules/@tollerud/ui/dist` |
| Turborepo / npm workspace app | `apps/web/app/globals.css` | `../../../node_modules/@tollerud/ui/dist` |
| pnpm (package in workspace) | `app/globals.css` | Prefer `@import "@tollerud/ui/source.css"` — hoisting varies |
| Bun | `app/globals.css` | Prefer `@import "@tollerud/ui/source.css"` |

```css
@import "@tollerud/ui/globals.css";
@source "../node_modules/@tollerud/ui/dist";
```

If styles disappear in production, the `@source` path is wrong — switch to `@tollerud/ui/source.css` or fix the relative path.

**Optional preset shim** — for extra theme tokens from `@tollerud/ui/preset` (`tollerud-preset.cjs`):

```ts
// tailwind.config.ts
import tollerudPreset from '@tollerud/ui/preset'
export default { presets: [tollerudPreset] }
```

```css
@import "tailwindcss";
@config "./tailwind.config.ts";
@import "@tollerud/ui/tokens.css";
@import "@tollerud/ui/globals-layers.css";
@import "@tollerud/ui/source.css";
```

### Tailwind v3 (legacy)

```ts
// tailwind.config.ts
import type { Config } from 'tailwindcss'
import tollerudPreset from '@tollerud/ui/preset'

const config: Config = {
  presets: [tollerudPreset],
  content: [
    './app/**/*.{ts,tsx}',
    './components/**/*.{ts,tsx}',
    './node_modules/@tollerud/ui/dist/**/*.{js,mjs}',
  ],
}

export default config
```

```css
/* app/globals.css */
@import "tailwindcss/preflight";
@import "tailwindcss/utilities";
@import "@tollerud/ui/globals-v3.css";
```

---

## Consumer styling policy

`@tollerud/ui` ships Tailwind support intentionally. Tailwind is the implementation engine for Tollerud UI, but consumer projects should treat the component API as the primary design language.

Use this order in apps:

1. Use exported `@tollerud/ui` components.
2. Use exported layout primitives and screen patterns when available.
3. Use Tailwind only for small local glue, such as spacing, alignment, or responsive visibility.
4. If a branded pattern repeats, add it to `@tollerud/ui` or create a local semantic feature component that composes `@tollerud/ui`.

### Allowed Tailwind glue

Small layout adjustments around package components are fine:

```tsx
import { Button, Card } from '@tollerud/ui'

export function DeployCard() {
  return (
    <Card>
      <p>Ready to deploy.</p>
      <div className="mt-6">
        <Button variant="primary">Deploy</Button>
      </div>
    </Card>
  )
}
```

### Avoid rebuilding branded UI with utilities

Do not recreate design-system primitives in app code:

```tsx
// Avoid: this bypasses Button variants, focus states, and brand tokens.
<button className="rounded-lg bg-yellow-400 px-4 py-2 text-black">
  Deploy
</button>
```

Do not recreate full branded page structure with raw layout utilities when a component or pattern should own it:

```tsx
// Avoid: move repeated branded layout into @tollerud/ui or a semantic feature component.
<section className="min-h-screen bg-black px-6 py-24">
  <div className="mx-auto grid max-w-6xl gap-6 md:grid-cols-3">
    {/* hand-built branded cards */}
  </div>
</section>
```

### Agent-safe recipes

Copy-paste screen compositions for common pages live on the docs site: **[Recipes](https://design.tollerud.dev/recipes/)** (`/recipes/`). Each recipe uses exported layout primitives and screen patterns, and links to a fuller interactive example where one exists (Blocks, Mission Control, Settings, Sign in, Onboarding, Data Table).

---

## Consumer project checklist

Run this before shipping or after onboarding a new app. It catches the styling drift that makes Tollerud projects look off-brand or lose production styles.

### Setup

- [ ] `@tollerud/ui` and required peers are installed (see [Install](#install)).
- [ ] `app/globals.css` imports **both** `@tollerud/ui/globals.css` and `@tollerud/ui/source.css`.
- [ ] `<Toaster />` is mounted near the app root when using Sonner toasts.
- [ ] Agents have [SKILL.md](SKILL.md) synced (`.claude/skills/tollerud-ui/SKILL.md` or equivalent).
- [ ] Common screens start from [Recipes](https://design.tollerud.dev/recipes/) or exported layout/screen patterns — not hand-built page grids.

### Automated audit

From your consumer app root:

```bash
npx tollerud-ui-audit
# monorepo app package:
npx tollerud-ui-audit ./apps/web
```

Equivalent when `npx` cannot resolve the bin:

```bash
node node_modules/@tollerud/ui/scripts/audit-consumer-styling.mjs
node node_modules/@tollerud/ui/scripts/audit-consumer-styling.mjs ./apps/web
```

**Exit codes:** `0` when no issues are found, or when only warnings exist and you passed `--warn-only`. `1` when one or more errors are found (fix before merge).

**Flags:**

| Flag | Effect |
|------|--------|
| `--warn-only` | Print findings but exit `0` even when errors are present — useful for advisory CI jobs |
| `./path/to/app` | Audit a monorepo package instead of the current working directory |

**What it checks:** missing `@tollerud/ui` in `package.json`, missing `globals.css` / `source.css` imports, local `components/ui` clones, `tollerud-*` classes without package imports, hardcoded brand hex values, local `cn()` helpers, nested `<Button><Link>` / `<button><a>`, and `components/ui` re-export shims that bypass the package.

Fix errors before merge; treat warnings as tech debt.

#### Audit error codes

Each finding prints `ERROR [code]` or `WARN [code]`. Use this table to fix issues:

| Code | Level | Meaning | Fix |
|------|-------|---------|-----|
| `missing-ui-dep` | error | `@tollerud/ui` not in `package.json` | Add the package and required peers — see [Install](#install) |
| `missing-globals-css` | error | Tailwind entry missing `@import "@tollerud/ui/globals.css"` | Add both `globals.css` and `source.css` imports below |
| `missing-source-css` | error | Tailwind entry missing `@import "@tollerud/ui/source.css"` | Without `source.css`, DS utility classes may be purged in production |
| `local-ui-clone` | error | `src/components/ui/` (or similar) contains copied primitives | Delete copies; `import { … } from '@tollerud/ui'` |
| `copied-ds-tokens` | error | File uses `tollerud-*` classes without importing `@tollerud/ui` | Replace vendored component files with package imports |
| `hardcoded-hex` | error | File hardcodes `#FFFF00`, `#0A0A0A`, `#E8D500`, etc. | Use `text-tollerud-yellow`, `bg-tollerud-noir-950`, `text-tollerud-yellow-warm` |
| `button-link-nesting` | error | `<Button>` or `<button>` wraps `<Link>` / `<a>` | `<Button asChild><Link … /></Button>` or `buttonVariants()` on the link |
| `ui-reexport-shim` | warn | `components/ui/index.ts` re-exports local copies | Import from `@tollerud/ui` directly |
| `local-cn` | warn | `lib/utils.ts` defines a local `cn()` | `import { cn } from '@tollerud/ui/utils'` |
| `generic-yellow-util` | warn | `bg-yellow-400` / `text-yellow-400` on branded UI | Prefer `<Button variant="primary">` or `text-tollerud-yellow` |
| `no-globals-css` | warn | No `globals.css` (or similar) found | Verify your Tailwind entry imports both Tollerud CSS files |
| `no-package-json` | warn | No `package.json` at audit root | Run from the consumer app package directory |

**CI example:**

```bash
npx tollerud-ui-audit
# advisory report (does not fail the job):
npx tollerud-ui-audit --warn-only
```

### Anti-patterns

| Symptom | Why it fails | Fix |
|---------|--------------|-----|
| `src/components/ui/Button.tsx` with `tollerud-btn` classes | Parallel design system; drifts from package | Delete; `import { Button } from '@tollerud/ui'` |
| `#FFFF00`, `#0A0A0A`, `bg-yellow-400` on branded UI | Hardcoded colors bypass tokens | `text-tollerud-yellow`, `bg-tollerud-noir-950` |
| Local `lib/utils.ts` with `cn()` | Duplicates package helper | `import { cn } from '@tollerud/ui/utils'` |
| Missing `@tollerud/ui/source.css` | Production purge drops DS classes | Add `@import "@tollerud/ui/source.css"` |
| `<Button><Link href="…">` | Invalid HTML; breaks focus/a11y | `<Button asChild><Link … /></Button>` or `buttonVariants()` on the link |
| Full pages built from `min-h-screen bg-black grid …` | Branded structure belongs in components | Use `PageShell`, `DashboardShell`, or a [recipe](https://design.tollerud.dev/recipes/) |

### Detect copied files manually

```bash
grep -rl "tollerud-yellow\|tollerud-noir\|tollerud-surface" src --include="*.tsx" --include="*.ts"
```

Files that match but do not import `@tollerud/ui` are likely vendored copies.

### Local feature components (not a parallel design system)

When a screen needs app-specific structure, compose **semantic feature components** that wrap `@tollerud/ui` exports — do not fork primitives into `components/ui`.

```tsx
// src/features/hosts/HostDeployPanel.tsx — app-specific, composes the package
import { Button, Card, FormPanel, Input, Stack } from '@tollerud/ui'

export function HostDeployPanel({ onDeploy }: { onDeploy: (host: string) => void }) {
  return (
    <FormPanel
      title="Connect host"
      description="SSH target for the Tollerud agent."
      footer={<Button variant="primary" onClick={() => onDeploy('emma.tollerud.no')}>Connect</Button>}
    >
      <Stack gap="md">
        <Input label="Hostname" placeholder="emma.tollerud.no" />
      </Stack>
    </FormPanel>
  )
}
```

Allowed Tailwind in feature components: small glue (`mt-6`, `flex justify-end`). The branded frame (`FormPanel`, `Button` variants, tokens) stays on the package.

---

## Required CSS and shared utilities

For Tailwind v4, keep both imports in your app stylesheet:

```css
@import "@tollerud/ui/globals.css";
@import "@tollerud/ui/source.css";
```

`globals.css` provides Tailwind, tokens, and component layers. `source.css` makes Tailwind scan the installed package so utility classes used only inside `@tollerud/ui` are generated.

Use the exported class merge helper instead of adding a local copy:

```tsx
import { cn } from '@tollerud/ui'
// or: import { cn } from '@tollerud/ui/utils'
```

---

## Subpath imports (tree-shaking)

Import individual components without pulling the full barrel:

```tsx
import { Button } from '@tollerud/ui/button'
import { PageShell, Section, Stack } from '@tollerud/ui'
import { PageHeader, ResourceList } from '@tollerud/ui'
import { cn } from '@tollerud/ui'  // or @tollerud/ui/utils for tree-shaking
```

See [SKILL.md](SKILL.md) for the export catalog, [PROPS.generated.md](PROPS.generated.md) for prop signatures, and [COMPONENTS.md](COMPONENTS.md) for usage examples.

---

## Server Components

`@tollerud/ui` ships client bundles with `'use client'`. Importing components (or `cn`, `buttonVariants`) from a Server Component file is safe — the import does not force your file to become a Client Component.

Use subpath imports (`@tollerud/ui/button`) for smaller client boundaries when splitting files manually.

---

## Migrating from copied components

Older projects sometimes copied `Button.tsx`, `lib/utils.ts`, or whole `components/ui/` trees from Tollerud UI. Replace them with package imports.

### Detect copied files

```bash
grep -rl "tollerud-yellow\|tollerud-noir\|tollerud-surface" src --include="*.tsx" --include="*.ts"
```

Also look for `components/ui/index.ts` re-exporting relative paths instead of `@tollerud/ui`.

### Fix checklist

1. Install the package and peers (see [Install](#install) above).
2. Replace local imports — `import { Button } from '@/components/ui/Button'` → `import { Button } from '@tollerud/ui'` (or `@tollerud/ui/button`).
3. Replace hand-rolled `cn()` — `import { cn } from '@tollerud/ui'` or `@tollerud/ui/utils`.
4. Delete copied files after imports compile.
5. Check **prop drift** against [SKILL.md](SKILL.md) — copied files may use outdated prop names (`onChange` vs `onValueChange`, etc.).
6. Replace hardcoded hex (`#FFFF00`, `#0A0A0A`) with tokens (`text-tollerud-yellow`, `bg-tollerud-noir-950`).
7. Add `<Toaster />` near the app root if you use Sonner toasts.
8. Run `npx tsc --noEmit` and fix type errors from signature changes.

| Copied pattern | Fix |
|----------------|-----|
| `src/components/ui/Button.tsx` | Delete; import from `@tollerud/ui` |
| Local `lib/utils.ts` with `cn()` | Delete; `import { cn } from '@tollerud/ui'` |
| `components/ui.ts` re-exporting relatives | Direct `@tollerud/ui` imports |

---

## AI agents

Copy-paste prompts for greenfield or existing projects: [Start with an AI agent](#start-with-an-ai-agent).

For day-to-day work, keep [SKILL.md](SKILL.md) synced in your project skills folder (`.claude/skills/tollerud-ui/SKILL.md` or equivalent) — it reflects actual exports and known gotchas. Re-sync when you bump `@tollerud/ui`.

See `README.md` for the complete setup guide.
