Files
greyhaven-design-system/README.md
Mathieu Virbel 60e2b045eb docs: README HTMX section — React-derivation, maintenance workflow, validation
- Make React-derivation explicit: the HTMX theme is generated from
  components/ui/*.tsx; never edit dist/greyhaven.htmx.css by hand.
- Fix stale CSS example: update @layer components → @layer utilities and
  show the :where() zero-specificity wrappers the generator now emits.
- Add "Maintenance — what to do when React changes" with explicit commands
  for cva tweaks, new components, generator bugs, and token updates.
- Point consumers to public/htmx.html as the 1:1 reference implementation
  and to GAPS.md for the generator edge-case log.
- Scope section updated: call out the vanilla-JS bridge that ships with
  public/htmx.html for checkbox/switch/tabs/toggle-group, separate from
  the positioning-heavy components that still need Alpine.
- Scripts Reference: add pnpm htmx-demo:build.
2026-04-24 14:52:30 -06:00

17 KiB

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

Quick Start

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.


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 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)

# 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:

  1. BRAND.md.claude/skills/greyhaven-brand.md (voice/tone/messaging)
  2. 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.

# 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 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:

# 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:

./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:

pnpm mcp:start

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

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

Install in Claude Desktop (claude_desktop_config.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:

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 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
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.

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).

@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

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

This copies:

  • dist/greyhaven.htmx.csspublic/css/greyhaven.htmx.css
  • Aspekta fonts → public/fonts/

Add to your Tailwind v4 input CSS:

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

Consume

<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):

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:

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:

# 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