# Greyhaven Design System

A framework-agnostic React component library built on Radix UI, Tailwind CSS v4, and shadcn/ui patterns. Designed for LLM consumption with a Claude Skill, MCP server, and Storybook documentation.

![Screenshot](docs/screenshot.png)

## Quick Start

```bash
pnpm install        # Install dependencies
pnpm dev            # Start showcase dev server (Next.js)
pnpm build          # Tokens + SKILL.md + production build
pnpm storybook      # Component catalog on http://localhost:6006
```

## Project Structure

```
greyhaven-design-system/
├── components/ui/          # 37+ framework-agnostic React components
├── tokens/                 # W3C DTCG design tokens (source of truth)
│   ├── color.json
│   ├── typography.json
│   ├── spacing.json
│   ├── radii.json
│   ├── shadows.json
│   └── motion.json
├── skill/                  # AI skills
│   ├── SKILL.md            # Design system reference (auto-generated)
│   ├── AGENTS.md           # Project instructions (auto-generated)
│   ├── BRAND.md            # Voice/tone/messaging (hand-curated, opt-in)
│   └── install.sh          # Installer (supports --brand-skill flag)
├── mcp/                    # MCP server for AI agents
│   └── server.ts
├── stories/                # Storybook stories (23 files, 8 categories)
├── lib/
│   ├── utils.ts            # cn() utility
│   └── catalog.ts          # Shared component catalog (used by MCP + SKILL.md)
├── scripts/
│   ├── generate-skill.ts    # SKILL.md generator
│   └── generate-htmx-css.ts # HTMX / framework-agnostic CSS generator
├── dist/
│   └── greyhaven.htmx.css  # Auto-generated CSS for HTMX/server-rendered projects
├── app/                    # Next.js showcase app (demo only)
└── style-dictionary.config.mjs
```

## Tech Stack

- **Components**: React 19, Radix UI, Tailwind CSS 4, CVA, tailwind-merge, clsx
- **Icons**: Lucide React
- **Forms**: React Hook Form + Zod
- **Tokens**: Style Dictionary v4 (W3C DTCG format)
- **Docs**: Storybook 10, auto-generated SKILL.md
- **AI Integration**: MCP server, Claude Skill

> **Framework-agnostic**: Components have zero Next.js imports. They work with Vite, Remix, Astro, CRA, or any React framework.

> **Also works without React**: `dist/greyhaven.htmx.css` exposes every component via `data-slot` / `data-variant` / `data-size` attribute selectors. HTMX, Django, Rails, Go template, Astro SSR — any project that emits HTML can consume the visual layer. See [HTMX / server-rendered usage](#htmx--server-rendered-usage).

---

## Using the Design System with AI

The design system provides four things for AI agents:

| File | What it is | Where it goes |
|------|-----------|---------------|
| **SKILL.md** | Full design system reference (tokens, all components, composition rules, extension protocol) | `.claude/skills/` — works in Claude Code, Cursor, OpenCode, Anigravity, etc. |
| **AGENTS.md** | Short project instructions telling the AI *how* to use the design system (follows the [agents.md](https://agents.md) convention) | Project root — copy as `AGENTS.md`, `CLAUDE.md`, `.cursorrules`, or `.github/copilot-instructions.md` |
| **BRAND.md** *(opt-in)* | Voice/tone/messaging rules for generating marketing copy, CTAs, product explanations. Hand-curated from the brand guidelines. | `.claude/skills/greyhaven-brand.md` — opt in via `--brand-skill` flag |
| **MCP Server** | Runtime tools for looking up components, validating colors, suggesting components, fetching brand rules, validating copy | Configured in `.mcp.json` |

SKILL.md and AGENTS.md are auto-generated from `tokens/*.json` and `lib/catalog.ts`. BRAND.md is hand-curated from `vibedocs/greyhaven-brand-system.md`.

### Quick Install (all at once)

```bash
# Default: SKILL.md + AGENTS.md + fonts
./skill/install.sh /path/to/your/project

# With brand skill: also install BRAND.md + logo SVGs
./skill/install.sh /path/to/your/project --brand-skill
```

This **copies** (not symlinks) the following into your project:
1. `SKILL.md` → `.claude/skills/greyhaven-design-system.md` (full reference)
2. `AGENTS.md` → project root (project-level instructions)
3. Aspekta font files → `public/fonts/`

With `--brand-skill`, additionally:

4. `BRAND.md` → `.claude/skills/greyhaven-brand.md` (voice/tone/messaging)
5. Logo SVGs → `public/logos/` (file names normalized: `gh-logo-positive-full-black.svg`, `gh-symbol-full-black.svg`, etc.)

The script also prints the CSS `@font-face` block and MCP server config to add next.

**Re-run the script after design system updates** to refresh your copies.

### SKILL.md (full reference)

The skill file gives any AI agent full design system context — every token, every component with props/variants/examples, composition rules, font setup, and the extension protocol.

**It's a global standard** — works with Claude Code, Cursor, OpenCode, Anigravity, and any tool that reads skill files.

```bash
# Via install script (recommended — also handles fonts + AGENTS.md)
./skill/install.sh /path/to/your/project

# Or copy manually
mkdir -p /path/to/your/project/.claude/skills
cp /path/to/greyhaven-design-system/skill/SKILL.md \
   /path/to/your/project/.claude/skills/greyhaven-design-system.md
```

### AGENTS.md (project instructions)

Short, directive instructions that tell the AI agent *how* to work in the project — use TypeScript, use semantic tokens, reference the MCP tools, etc. Follows the [agents.md](https://agents.md) convention so it works with most AI coding tools out of the box.

**Copy it to your project root** under whichever name your tool reads:

```bash
# Standard (agents.md convention — Cursor, OpenCode, Windsurf, Aider, etc.)
cp /path/to/greyhaven-design-system/skill/AGENTS.md /path/to/your/project/AGENTS.md

# Claude Code
cp /path/to/greyhaven-design-system/skill/AGENTS.md /path/to/your/project/CLAUDE.md

# Cursor (legacy)
cp /path/to/greyhaven-design-system/skill/AGENTS.md /path/to/your/project/.cursorrules

# GitHub Copilot
mkdir -p /path/to/your/project/.github
cp /path/to/greyhaven-design-system/skill/AGENTS.md /path/to/your/project/.github/copilot-instructions.md
```

Or use the install script, which copies `AGENTS.md` to the project root automatically.

### BRAND.md (voice, tone, messaging)

BRAND.md is an **opt-in** skill for projects that generate user-facing content — marketing copy, landing pages, CTAs, product explanations, emails. It codifies the Greyhaven brand voice: direct, plain-spoken, engineering-flavored, no hype, no sales language.

It's **not installed by default** because most projects only need the design system components, not brand voice rules.

**Install via the `--brand-skill` flag:**

```bash
./skill/install.sh /path/to/your/project --brand-skill
```

This adds:
- `skill/BRAND.md` → `.claude/skills/greyhaven-brand.md` (brand skill)
- Greyhaven logo SVGs → `public/logos/` (full logos + symbol-only + product lockups, in black and white variants, file names normalized)

Once installed, AI agents in your project can reference the brand skill when generating copy. The skill covers:
- Core positioning and the three brand axes (containment, human-centered, engineered)
- Tone of voice rules
- Writing patterns (plain-language engineering, no hype)
- Reasoning patterns (cause→effect, constraint→outcome, observation→explanation, finite scope→concrete result)
- CTA guidance (good vs. bad patterns)
- Logo usage rules
- A self-check list to run before shipping any copy

### Option C: MCP Server

The MCP server provides 7 tools for programmatic access:

| Tool | Description |
|------|-------------|
| `get_tokens(category?)` | Returns token values (all, or filtered by: color, typography, spacing, radii, shadows, motion) |
| `get_component(name)` | Returns full component spec + source code |
| `list_components(category?)` | Lists components (all, or by: primitives, layout, overlay, navigation, data, feedback, form, composition) |
| `validate_colors(code)` | Checks code for raw hex values that should use design tokens |
| `suggest_component(description)` | Suggests components for a described UI need |
| `get_brand_rules(section?)` | Returns brand voice/tone/messaging rules. Section can be: `positioning`, `axes`, `tone`, `writing-rules`, `reasoning-patterns`, `cta`, `logo`, `self-check`, or `all` |
| `validate_copy(text)` | Lints marketing copy for hype words, sales language, vague superlatives, urgency framing, and exclamation marks |

Plus resources: `tokens://all`, `component://{name}` for each component, and `brand://guidelines` for the full brand skill.

**Run directly:**

```bash
pnpm mcp:start
```

**Install in Claude Code (`.mcp.json` in your project root):**

```json
{
  "mcpServers": {
    "greyhaven": {
      "command": "npx",
      "args": ["tsx", "/absolute/path/to/greyhaven-design-system/mcp/server.ts"]
    }
  }
}
```

**Install in Claude Desktop (`claude_desktop_config.json`):**

```json
{
  "mcpServers": {
    "greyhaven": {
      "command": "npx",
      "args": ["tsx", "/absolute/path/to/greyhaven-design-system/mcp/server.ts"]
    }
  }
}
```

After adding, restart Claude Code / Claude Desktop. The tools will appear automatically.

**Test it works:**

```bash
echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}' | pnpm mcp:start
```

You should see a JSON response with `"serverInfo":{"name":"greyhaven-design-system"}`.

---

## Design Tokens

Tokens are defined in `tokens/*.json` using the [W3C Design Token Community Group](https://tr.designtokens.org/format/) format. Style Dictionary v4 generates:

| Output | Path | Purpose |
|--------|------|---------|
| CSS (light) | `app/tokens/tokens-light.css` | `:root` CSS custom properties |
| CSS (dark) | `app/tokens/tokens-dark.css` | `.dark` CSS custom properties |
| TypeScript | `app/tokens/tokens.ts` | Type-safe token constants |
| Markdown | `app/tokens/TOKENS.md` | Reference doc |

```bash
pnpm tokens:build   # Regenerate all outputs from tokens/*.json
```

---

## Storybook

23 story files across 8 categories with autodocs, theme switching (light/dark via toolbar), and all component variants.

```bash
pnpm storybook         # Dev server on http://localhost:6006
pnpm build-storybook   # Static build
```

---

## HTMX / server-rendered usage

The React components assume a React runtime. For HTMX, Django templates, Rails ERB, Go `html/template`, Astro SSR, or any other server-rendered stack, consume the design system via the auto-generated CSS layer.

**The HTMX theme is derived from React.** React's `components/ui/*.tsx` is the single source of truth: the generator AST-walks each component, extracts its `cva()` bindings and static `className` strings, and emits CSS rules keyed on the same `data-slot` / `data-variant` / `data-size` attributes React already sets. If a `.tsx` file doesn't use it, the HTMX layer doesn't have it. Never edit `dist/greyhaven.htmx.css` by hand.

### What you get

`dist/greyhaven.htmx.css` is ~100KB with ~300 rules emitted into `@layer utilities`, one per data-slot, with attribute selectors for variants and sizes. Selectors are wrapped in `:where()` for zero specificity so consumer `className` overrides still win (matching React + tailwind-merge behavior).

```css
@layer utilities {
  :where([data-slot="card"]) { @apply bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm; }
  :where([data-slot="card-header"]) { @apply grid auto-rows-min grid-rows-[auto_auto] items-start gap-2 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6; }
  :where([data-slot="card-title"]) { @apply leading-none font-semibold; }

  :where([data-slot="button"]) { @apply inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium …; }
  :where([data-slot="button"]):where(:not([data-variant])) { @apply bg-primary text-primary-foreground hover:bg-primary/90; }
  :where([data-slot="button"]):where([data-variant="default"]) { @apply bg-primary text-primary-foreground hover:bg-primary/90; }
  :where([data-slot="button"]):where([data-variant="outline"]) { @apply border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground; }
  :where([data-slot="button"]):where([data-size="sm"]) { @apply h-8 rounded-md gap-1.5 px-3; }
}
```

### Install

```bash
./skill/install.sh /path/to/your/project --htmx-css
```

This copies:
- `dist/greyhaven.htmx.css` → `public/css/greyhaven.htmx.css`
- Aspekta fonts → `public/fonts/`

Add to your Tailwind v4 input CSS:

```css
@import "tailwindcss";
@import "./tokens-light.css";
@import "./tokens-dark.css";
@import "./greyhaven.htmx.css";
```

### Consume

```html
<div data-slot="card">
  <div data-slot="card-header">
    <div data-slot="card-title">Requests Over Time</div>
    <div data-slot="card-description">Last 24 hours</div>
  </div>
  <div data-slot="card-content">…</div>
</div>

<button data-slot="button" data-variant="default">Save</button>
<button data-slot="button" data-variant="outline" data-size="sm">Cancel</button>
<span data-slot="badge" data-variant="success">Active</span>
```

See `public/htmx.html` for a full reference implementation — every component in the React showcase has a matching static-HTML section.

### Scope

- **Static visual components** (Card, Button, Badge, Input, Label, Textarea, Table, Separator, Code, Kbd, Progress, Avatar, Skeleton, Alert, Pagination, Breadcrumb, Navbar, etc.) → fully driven by CSS, no JS needed.
- **Interactive components with simple state toggles** (Checkbox, Switch, Tabs, ToggleGroup) → `public/htmx.html` ships a ~60-line vanilla-JS delegation bridge that flips `data-state` on click. Copy or adapt it into your consumer.
- **Interactive components with positioning** (Dialog, Dropdown, Popover, Select, Combobox, Tooltip) → CSS emits their per-state visual rules, but open/close, portaling, and focus management are the consumer's responsibility. Alpine.js pairs naturally with HTMX for these.
- **Native HTML alternatives**: `<details>` covers Accordion/Collapsible, `<dialog>` covers Dialog. The CSS rules apply to those too.

### Maintenance — what to do when React changes

Because the HTMX layer is generated from React, **React is the only place to edit component styles**. Every consumer-facing change follows the same flow.

**When any `components/ui/*.tsx` changes** (tweaked cva, new variant, renamed slot):

```bash
pnpm htmx-css:build        # regenerate dist/greyhaven.htmx.css
pnpm htmx-demo:build       # recompile public/htmx.css (Tailwind over the new layer)
```

Then capture a screenshot diff to confirm nothing drifted:

```bash
pnpm dev                              # serve React at /, HTMX mirror at /htmx.html
# capture each section as -react.webp and -htmx.webp (via Charlotte or equivalent)
bash htmx-demo/compare-all.sh         # 22-section diff, threshold 99%
```

**When a new component is added** (new `.tsx` with its own `data-slot`):

1. `pnpm htmx-css:build` — generator picks it up from the AST automatically.
2. Add a mirror section to `public/htmx.html` with the right `data-slot` / `data-state` / `data-variant` / `data-size` attributes on static DOM.
3. If the component needs state toggling, extend the vanilla JS bridge at the bottom of `public/htmx.html` (existing `checkbox` / `switch` / `tabs-trigger` / `toggle-group-item` branches are templates).
4. Add the section name to `SECTIONS[]` in `htmx-demo/compare-all.sh`.
5. Capture screenshots, run `compare-all.sh`, confirm ≥99%.

**When the generator emits something wrong** (padding lost, state rule missing, specificity issue):

Check `GAPS.md` first — most generator edge cases are already documented there with root cause + fix. If it's new, fix the generator (never patch the consumer), add a `GAPS.md` entry, and re-run the demo build + diff sweep.

**When tokens change** (`app/tokens/tokens-*.css`):

Consumers copy those files locally (they're not generated into `dist/`). Consumer bumps the commit hash in their import and re-runs their Tailwind build. No action inside this repo.

**Pushing changes to a consumer:**

```bash
# In the consumer repo, after this design system has been updated:
/path/to/greyhaven-design-system/skill/install.sh . --htmx-css
# then re-run the consumer's Tailwind build, e.g.:
npm run build:css
```

---

## Adding a New Component

1. Create `components/ui/my-component.tsx` following the CVA pattern (see `button.tsx`)
2. Add it to the catalog in `lib/catalog.ts`
3. Create a story in `stories/<Category>/MyComponent.stories.tsx`
4. Run `pnpm skill:build` to regenerate SKILL.md (or just `pnpm build`)
5. The MCP server picks it up automatically (reads `lib/catalog.ts` at runtime)

---

## Scripts Reference

| Script | Description |
|--------|-------------|
| `pnpm dev` | Start Next.js showcase dev server |
| `pnpm build` | Full build: tokens + SKILL.md + Next.js |
| `pnpm storybook` | Storybook dev server on :6006 |
| `pnpm build-storybook` | Static Storybook build |
| `pnpm tokens:build` | Regenerate CSS/TS/MD from token JSON files |
| `pnpm skill:build` | Regenerate skill/SKILL.md and skill/AGENTS.md from tokens + catalog |
| `pnpm htmx-css:build` | Regenerate dist/greyhaven.htmx.css from components/ui/*.tsx |
| `pnpm htmx-demo:build` | Recompile public/htmx.css (Tailwind over the generated layer) for the HTMX showcase |
| `pnpm mcp:start` | Start the MCP server (stdio transport) |
| `pnpm mcp:build` | Type-check MCP server |
| `pnpm lint` | Run ESLint |
