AGENTS.md
Everything an AI agent needs to work effectively in this codebase. This is a copy of the project's CLAUDE.md file.
Package Manager
This project uses pnpm. Always use pnpm for all commands — never npm or yarn.
pnpm dev # start dev server
pnpm build # production build
pnpm lint # lint
pnpm install # install dependenciesFolder Structure
src/
app/ Next.js App Router — routing only, no business logic
layout.tsx Root layout: fonts, Providers
(home)/page.tsx Home page
Providers.tsx Client wrapper for Tooltip.Provider (Base UI)
global.css Tailwind v4 theme + design tokens
base/ All starter code — the fork boundary lives here
layout/ App-level layout (Navbar, NavbarCentered, NavbarStatic, Footer, MobileMenu)
components/ Shared components (PageHero, etc.)
features/ Feature modules — add one folder per feature
ui/ Design system primitives — generic, no business logic
icons/ Icon components
constants/ Site-wide constants (brand, nav, contact, social)
types/ Project-specific TypeScript types
lib/ Utilities (cn, etc.)Import Rules
ui/components have no knowledge of the app — no imports from constants, features, etc.layout/can import from constants, ui, and components but not featurescomponents/can import from constants and ui but not features or layoutfeatures/can import from anywhere except other features- Pages (
app/) orchestrate features and sections — keep them thin - Nothing outside
app/should import fromapp/— it is the top of the dependency graph
File Rules
- Always create a barrel index.ts in each feature folder
- Never import directly from third-party UI libraries — all primitives must go through @/ui wrappers
- No inline style props except for truly dynamic values
- One component per file (tightly coupled sub-components are the only exception)
- ui/ components accept className for layout overrides — they never hardcode margins or positioning
- Feature components own their spacing and do not accept className
Color System
Defined in global.css using Tailwind v4 @theme. All Tailwind built-in colors are disabled — only the custom tokens are available.
| Token | Usage |
|---|---|
| root | Page background |
| root-100 | Section / card surface |
| root-200 | Elevated surface |
| root-contrast | Primary text on root backgrounds |
| root-contrast-subtle | Secondary / muted text |
| primary | Brand accent background |
| secondary | Second brand color |
| divider | Lines, borders |
| overlay | Semi-opaque dark layer for modals |
Color Rules
- All colors must be defined as named tokens in global.css — never use raw hex values
- Pair every background token with its
-contrastcompanion — bg-primary → text-primary-contrast - Use Tailwind opacity modifier for transparency (bg-root-contrast/50)
- Dark mode is opt-in via .dark class — tokens flip automatically
Naming Conventions
- Feature folders and route segments use kebab-case, component files use PascalCase, utility files use camelCase
- Named exports only — no default exports
- Event handler props prefixed with
on, handler functions withhandle - Boolean props prefixed with
is,has, orcan
TypeScript
- No any — use unknown and narrow
- No as type casting unless there is no alternative
- Component props interfaces are defined in the same file as the component
Tailwind
This project uses Tailwind v4 with the @theme directive in global.css. No tailwind.config.ts — configuration lives in global.css.
- Data attribute variants use the v4 syntax:
data-open:opacity-100 - All custom tokens are available as Tailwind utilities automatically
cn()from @/lib merges class names with clsx + tailwind-merge
UI Imports
Always import from @/ui, never from individual files. Icons are the exception — import those from @/ui/icons.
// correct
import { Button, Heading, Text } from "@/ui";
import { MenuIcon, ArrowForwardIcon } from "@/ui/icons";
// wrong
import { Button } from "@/ui/Button";
import { MenuIcon } from "@/ui";Server vs Client Components
- Default to server components — no "use client" unless needed
- Add "use client" only when using: useState, useEffect, useRef, event handlers, browser APIs
- Layout components (Navbar, Footer) are client components
- Children passed into a client component remain server-rendered
Adding a New Page
- Create
src/app/[route]/page.tsx - Render the page's sections directly — layout chrome (navbar, footer) comes from the root layout
- Add the route to NAV_LINKS in constants/navigation.ts
- Add the route to sitemap.ts
- Create a feature folder if the page has significant content
export default function NewPage() {
return (
<>
{/* page sections */}
</>
);
}Adding a New Feature
src/base/features/my-feature/
MyComponent.tsx
index.ts <- export everything from hereimport { MyComponent } from "@/features/features/my-feature";Full CLAUDE.md
Copy and paste this into your project's CLAUDE.md file.
# Marketing Starter — Agent Guide
A Next.js 15 marketing site starter. This document covers everything an agent needs to work effectively in this codebase.
## Package Manager
This project uses **pnpm**. Always use `pnpm` for all commands — never `npm` or `yarn`.
```bash
pnpm dev # start dev server
pnpm build # production build
pnpm lint # lint
pnpm install # install dependencies
```
---
## Folder Structure
```
src/
app/ # Next.js App Router — routing only, no business logic
layout.tsx # Root layout: fonts, Providers (html/body shell only)
(home)/page.tsx # Home page
about/page.tsx
services/page.tsx
contact/page.tsx
Providers.tsx # Client wrapper for Tooltip.Provider (Base UI)
global.css # Tailwind v4 theme + design tokens
base/ # All starter code — the "fork boundary" lives here
layout/ # App-level layout (Navbar, NavbarCentered, NavbarStatic, Footer, MobileMenu)
components/ # Shared components (PageHero, etc.)
features/ # Feature modules — add one folder per feature
example/
ExampleComponent.tsx
index.ts
ui/ # Design system primitives — generic, no business logic
icons/ # Icon components
constants/ # Site-wide constants (brand, nav, contact, social)
types/ # Project-specific TypeScript types
lib/ # Utilities (cn, etc.)
```
Everything you'd keep in a fork lives under `src/base/`. The root `layout.tsx` stays in `src/app/` because Next.js requires it there.
**Rules:**
- `base/ui` components have no knowledge of the app — no imports from `@/constants`, `@/features/features`, etc.
- `base/layout` can import from `@/constants`, `@/ui`, and `@/features/components` but not `@/features/features`
- `base/components` can import from `@/constants` and `@/ui` but not `@/features/features` or `@/features/layout`
- `base/features` components can import from anywhere in `@/base` except other features
- Pages (`app/`) orchestrate features and sections — keep them thin
- Always create a barrel `index.ts` in each feature folder
- Never import directly from third-party UI libraries (Base UI, Radix, Headless UI, etc.) in `features/`, `components/`, or pages — all primitives must be consumed through `@/ui` wrappers only
- Always import features through their barrel (`@/features/features/hero`, never `@/features/features/hero/HeroSection`)
- Nothing outside `app/` should import from `app/` — it is the top of the dependency graph
- `lib/` utilities must be pure functions — no React imports, no side effects, no app-specific knowledge
- `constants/` is static data only — no functions, no logic; use `as const` for type safety
- `types/` is for shared types only — feature-specific types stay colocated in the feature folder and are not exported via `@/types`
- No inline `style={{}}` props except for truly dynamic values that cannot be expressed in Tailwind (e.g., a JS-calculated pixel height)
- No one-off Tailwind arbitrary values (`w-[347px]`) — if a value recurs, add it as a token in `global.css`
- Exception: z-index values beyond the default scale (`z-60`, `z-70`, etc.) are allowed without brackets — Tailwind v4 accepts any numeric z-index directly
- Prefer canonical Tailwind classes over arbitrary values when equivalent — e.g. `-top-1` instead of `top-[-4px]`
- `ui/` components accept `className` as a prop to allow layout overrides from outside — they never hardcode their own margins or positioning
- Feature components own their own spacing/layout and do not accept `className` — they are not generic
- `components/` and `features/` components must use `<Container>` from `@/ui` as their outer wrapper for consistent max-width and responsive padding
- One component per file — tightly coupled sub-components are the only exception
- Each page's sections should be discrete components, not one large JSX block in the page file
---
## Color System
Defined in `src/app/global.css` using Tailwind v4 `@theme`. All Tailwind built-in colors are disabled (`--color-*: initial`) — only the tokens below are available.
### Semantic tokens
Every background token has a guaranteed-contrast `-contrast` foreground companion.
| Token | Usage |
|-------|-------|
| `root` | Page background |
| `root-100` | Section / card surface (slightly different from root) |
| `root-200` | Elevated surface |
| `root-contrast` | Primary text on root backgrounds |
| `root-contrast-subtle` | Secondary / muted text |
| `primary` | Brand accent background |
| `primary-contrast` | Text on primary backgrounds |
| `secondary` | Second brand color |
| `secondary-contrast` | Text on secondary backgrounds |
| `info` / `info-contrast` | Informational state |
| `success` / `success-contrast` | Success state |
| `warning` / `warning-contrast` | Warning state |
| `danger` / `danger-contrast` | Danger / destructive state |
| `divider` | Lines, borders |
| `overlay` | Semi-opaque dark layer (80%) for modals |
**Dark mode** is opt-in via `.dark` class on any ancestor. Tokens flip automatically.
**Rules:**
- All Tailwind built-in colors are disabled — only theme tokens are available as utilities
- All colors must be defined as named tokens in `global.css` — never use raw hex values or CSS color names
- If a new color is needed, add it to `global.css` first — never introduce a color directly in a component
- Pair every background token with its `-contrast` companion — `bg-primary` → `text-primary-contrast`
- Use Tailwind's opacity modifier for transparency (`bg-root-contrast/50`) — do not add separate opacity tokens
- Never set color via inline `style={{color: '...'}}` — all color must go through Tailwind utility classes
---
## Naming Conventions
- Feature folders and route segments use kebab-case (`my-feature`), component files use PascalCase (`MyComponent.tsx`), utility files use camelCase (`formatDate.ts`)
- Named exports only — no default exports
- Event handler props are prefixed with `on` (`onSubmit`), handler functions with `handle` (`handleSubmit`)
- Boolean props are prefixed with `is`, `has`, or `can` (`isOpen`, `hasError`)
---
## TypeScript
- No `any` — if a type is unknown, use `unknown` and narrow it
- No `as` type casting unless there is no alternative
- Component props interfaces are defined in the same file as the component, not in `types/`
- When a component prop conflicts with a legacy HTML attribute (e.g. `color`), use `Omit<React.HTMLAttributes<...>, "color">` before extending
---
## Tailwind
This project uses **Tailwind v4** with the `@theme` directive in `global.css`.
- No `tailwind.config.ts` — configuration lives in `global.css`
- Data attribute variants use the v4 syntax: `data-open:opacity-100` (not `data-[open]:opacity-100`)
- All custom tokens are available as Tailwind utilities automatically: `bg-root`, `text-primary`, `border-divider`, etc.
- `cn()` from `@/lib` merges class names with `clsx` + `tailwind-merge`
---
## UI Components (`@/ui`)
Always import from `@/ui`, never from individual files. Icons are the exception — import those from `@/ui/icons`:
```tsx
// correct
import { Button, Heading, Text } from "@/ui";
import { MenuIcon, ArrowForwardIcon } from "@/ui/icons";
// wrong
import { Button } from "@/ui/Button";
import { MenuIcon } from "@/ui";
```
---
## Accessibility (a11y)
- `IconButton` automatically wraps in a `Tooltip` when `aria-label` is provided — always pass `aria-label` on icon-only buttons
- Use semantic HTML: `<nav aria-label="...">`, `<footer aria-label="...">`, `<main>`, `<section>`
- `Heading` renders `h2` by default — pass `as="h1"` on page-level headings
- Interactive elements that aren't `<button>` or `<a>` need `role` + `tabIndex` + keyboard handlers
- Focus styles use `focus-visible:ring-2 focus-visible:ring-primary focus-visible:outline-none`
- Images always require meaningful `alt` text (empty string only for decorative images)
---
## Server vs Client Components
- **Default to server components** — no `"use client"` unless needed
- Add `"use client"` only when using: useState, useEffect, useRef, event handlers, browser APIs
- `children` passed into a client component remain server-rendered
- Layout components (Navbar, Footer) are client components (scroll listeners, pathname)
- Feature components that manage local UI state are client components
---
## SEO
- Every page must export a `metadata` object — no page ships without `title` and `description`
- Set `openGraph.images` on any page likely to be shared or linked externally
---
## Constants
Update `src/base/constants/brand.ts` with your company info before building features:
```ts
export const BRAND = {
name: "Company Name",
initials: "CO",
tagline: "Your tagline here.",
year: 2025,
location: "City, State",
};
```
All constants are imported via `@/constants`:
```tsx
import { BRAND, CONTACT, NAV_LINKS } from "@/constants";
```
---
## Adding a New Page
1. Create `src/app/[route]/page.tsx`
2. Render the page's sections directly — layout concerns (navbar, footer) come from the root layout
3. Add the route to `NAV_LINKS` in `src/base/constants/navigation.ts`
4. Add the route to `sitemap.ts`
5. Create a feature folder if the page has significant content: `src/base/features/[name]/`
```tsx
export default function NewPage() {
return (
<>
{/* page sections */}
</>
);
}
```
---
## Adding a New Feature
```
src/base/features/my-feature/
MyComponent.tsx
index.ts <- export everything from here
```
Import in pages as:
```tsx
import { MyComponent } from "@/features/features/my-feature";
```