diff --git a/README.md b/README.md index 2060427..f205564 100644 --- a/README.md +++ b/README.md @@ -55,61 +55,62 @@ greyhaven-design-system/ ## Using the Design System with AI -The design system provides three complementary ways for AI agents to consume it: +The design system provides three things for AI agents: -| | Claude Skill (SKILL.md) | AGENT.md | MCP Server | -|---|---|---|---| -| **What it is** | Claude Code skill file | Generic AI agent instructions | Running process with tools | -| **Stays in sync** | Yes -- auto-generated | Yes -- auto-generated | Yes -- reads source at runtime | -| **Setup** | Symlink to `.claude/skills/` | Symlink to project root | Configure in settings | -| **Best for** | Claude Code | Cursor, Copilot, Windsurf, Codeium | Programmatic access, validation | +| 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. | +| **AGENT.md** | Short project instructions telling the AI *how* to use the design system (like CLAUDE.md but portable) | Project root — copy as `AGENT.md`, `CLAUDE.md`, `.cursorrules`, or `.github/copilot-instructions.md` | +| **MCP Server** | Runtime tools for looking up components, validating colors, suggesting components | Configured in `.mcp.json` | -All three read from the same sources (`tokens/*.json` and `lib/catalog.ts`), so they always agree. +All are auto-generated from the same sources (`tokens/*.json` and `lib/catalog.ts`). ### Quick Install (all at once) -The install script sets up everything -- Claude Skill, AGENT.md, and fonts: - ```bash ./skill/install.sh /path/to/your/project ``` This will: -1. Symlink `SKILL.md` into `.claude/skills/` (for Claude Code) -2. Symlink `AGENT.md` into the project root (for Cursor, Copilot, etc.) +1. Symlink `SKILL.md` into `.claude/skills/` (full reference for any AI agent) +2. Copy `AGENT.md` into the project root (project-level instructions) 3. Copy Aspekta font files into `public/fonts/` 4. Print CSS import instructions -### Option A: Claude Skill (SKILL.md) +### SKILL.md (full reference) -The skill file gives any Claude Code session full design system context -- tokens, all components with props/examples, composition rules, font setup, and the extension protocol. +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. -**Install into a consuming project:** +**It's a global standard** — works with Claude Code, Cursor, OpenCode, Anigravity, and any tool that reads skill files. ```bash -# From the design system repo +# Via install script ./skill/install.sh /path/to/your/project # Or manually mkdir -p /path/to/your/project/.claude/skills ln -sf /absolute/path/to/greyhaven-design-system/skill/SKILL.md \ - /path/to/your/project/.claude/skills/greyhaven.md + /path/to/your/project/.claude/skills/greyhaven-design-system.md ``` -**Regenerate after changes:** +### AGENT.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. This is the equivalent of `CLAUDE.md` but portable across tools. + +**Copy it to your project root** under whichever name your tool reads: ```bash -pnpm skill:build -``` +# Claude Code +cp /path/to/greyhaven-design-system/skill/AGENT.md /path/to/your/project/CLAUDE.md -This is run automatically as part of `pnpm build`. If you add a component, add it to `lib/catalog.ts` and regenerate. +# Cursor +cp /path/to/greyhaven-design-system/skill/AGENT.md /path/to/your/project/.cursorrules -### Option B: AGENT.md (non-Claude AI agents) +# GitHub Copilot +mkdir -p /path/to/your/project/.github +cp /path/to/greyhaven-design-system/skill/AGENT.md /path/to/your/project/.github/copilot-instructions.md -For Cursor, GitHub Copilot, Windsurf, Codeium, and other AI coding assistants that read project-root markdown files: - -```bash -# Via install script (also sets up fonts + Claude Skill) +# Or just use the install script which symlinks AGENT.md to the root ./skill/install.sh /path/to/your/project # Or manually diff --git a/scripts/generate-skill.ts b/scripts/generate-skill.ts index 511faa1..dbe4a22 100644 --- a/scripts/generate-skill.ts +++ b/scripts/generate-skill.ts @@ -51,6 +51,7 @@ function componentCount(): number { function buildDesignPhilosophy(): string { return `## Design Philosophy +- **TypeScript only**: All code MUST be written in TypeScript (\`.tsx\` / \`.ts\`). Never generate plain JavaScript (\`.jsx\` / \`.js\`). - **Minimal and restrained**: Off-white + off-black + warm greys. One single accent color (orange). No gradients, no decorative color, no multiple accent hues. - **Typography-driven**: Source Serif 4/Pro (serif) for headings and body content. Aspekta (sans, self-hosted) for UI labels, buttons, navigation, and form elements. - **Calm, professional aesthetic**: Tight border-radii, subtle shadows, generous whitespace. @@ -170,7 +171,7 @@ function buildCompositionRules(): string { - **Form layout**: Vertical stack with \`gap-4\`, labels above inputs - **Navbar**: Fixed top, \`z-50\`, \`h-16\`, logo left, nav center, actions right - **Typography pairing**: Serif (\`font-serif\`) for content headings, sans (\`font-sans\`) for UI labels/buttons -- **Color restraint**: Orange ONLY for primary actions and key emphasis -- never decorative +- **Color restraint**: Trust the default component variants for orange accent -- they apply it at the right scale. Don't apply \`bg-primary\` to large surfaces, containers, or section backgrounds - **Focus pattern**: \`focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]\` - **Disabled pattern**: \`disabled:pointer-events-none disabled:opacity-50\` - **Aria-invalid pattern**: \`aria-invalid:ring-destructive/20 aria-invalid:border-destructive\` @@ -242,7 +243,7 @@ function generateSkill(): string { > **Stack**: React 19, Radix UI, Tailwind CSS v4, CVA, tailwind-merge, clsx, Lucide icons > **Framework-agnostic**: No Next.js imports. Works with Vite, Remix, Astro, CRA, or any React setup. -This skill gives you full context to generate pixel-perfect, on-brand UI using the Greyhaven Design System. Every component lives in \`components/ui/\`. Use semantic tokens, never raw colors. Follow the patterns exactly. +This skill gives you full context to generate pixel-perfect, on-brand UI using the Greyhaven Design System. Every component lives in \`components/ui/\`. Use semantic tokens, never raw colors. Follow the patterns exactly. **ALWAYS use TypeScript (.tsx/.ts) — never plain JavaScript.** `, '---\n', buildDesignPhilosophy(), @@ -260,41 +261,73 @@ This skill gives you full context to generate pixel-perfect, on-brand UI using t } // --------------------------------------------------------------------------- -// AGENT.md (non-Claude AI agents: Cursor, Copilot, Windsurf, etc.) +// AGENT.md (project-level instructions for non-Claude AI agents) // --------------------------------------------------------------------------- function generateAgent(): string { - return [ - `# Greyhaven Design System + // Count components by category for the summary + const categories = new Map() + for (const c of COMPONENT_CATALOG) { + categories.set(c.category, (categories.get(c.category) || 0) + 1) + } + const categorySummary = Array.from(categories.entries()) + .map(([cat, count]) => `${cat} (${count})`) + .join(', ') -> **Auto-generated** by \`scripts/generate-skill.ts\` -- DO NOT EDIT by hand. + return `# Project Instructions + +> **Auto-generated** by the Greyhaven Design System. > Re-generate: \`pnpm skill:build\` in the design system repo. > -> This file provides AI coding assistants (Cursor, GitHub Copilot, Windsurf, -> Codeium, etc.) with full context about the Greyhaven Design System. +> Copy this file to your project root as \`AGENT.md\`, \`.cursorrules\`, +> or \`.github/copilot-instructions.md\` depending on your AI tool. -## How to Use This +This project uses the **Greyhaven Design System**. -When building UI in this project, follow the Greyhaven Design System: -- Import components from \`components/ui/\` (or \`@/components/ui/\` with alias) -- Use semantic Tailwind classes (\`bg-primary\`, \`text-foreground\`, \`border-border\`) -- never raw hex colors -- Use \`font-sans\` (Aspekta) for UI elements, \`font-serif\` (Source Serif) for content -- Orange (\`#D95E2A\` / \`bg-primary\`) is the ONLY accent color -- use sparingly -- All components are framework-agnostic React (no Next.js imports) -`, - '---\n', - buildDesignPhilosophy(), - '---\n', - buildFontSetup(), - '---\n', - buildTokenReference(), - '---\n', - buildComponentCatalog(), - '---\n', - buildCompositionRules(), - '---\n', - buildExtensionProtocol(), - ].join('\n') +## Rules + +- **ALWAYS use TypeScript** (\`.tsx\` / \`.ts\`). NEVER generate plain JavaScript (\`.jsx\` / \`.js\`). +- Use the \`greyhaven\` SKILL.md for full design system context (tokens, components, composition rules). It should be installed at \`.claude/skills/greyhaven-design-system.md\` or accessible to your AI tool. +- If the \`greyhaven\` MCP server is available, use its tools: + - \`list_components()\` to find the right component for a UI need + - \`get_component(name)\` to get exact props, variants, and usage examples + - \`validate_colors(code)\` to check code for off-brand colors + - \`suggest_component(description)\` to get recommendations +- Import components from \`components/ui/\` (or \`@/components/ui/\` with path alias) +- Never use raw hex colors -- use semantic Tailwind classes (\`bg-primary\`, \`text-foreground\`, \`border-border\`, etc.) +- Use \`font-sans\` (Aspekta) for UI elements: buttons, nav, labels, forms +- Use \`font-serif\` (Source Serif) for content: headings, body text +- Trust the design system's default component variants for accent -- they apply orange at the right scale. Don't apply \`bg-primary\` to large surfaces, containers, or section backgrounds +- All components are framework-agnostic React (no Next.js, no framework-specific imports) +- Dark mode is toggled via the \`.dark\` class -- use semantic tokens that adapt automatically + +## Component Summary + +${componentCount()} components across ${categories.size} categories: ${categorySummary}. + +For full component specs, props, and examples, refer to the SKILL.md file or use the MCP \`get_component(name)\` tool. + +## Key Patterns + +- **CVA variants**: Components use \`class-variance-authority\` for variant props +- **Slot composition**: Components use \`data-slot="name"\` attributes +- **Class merging**: Always use \`cn()\` from \`@/lib/utils\` (clsx + tailwind-merge) +- **Focus rings**: \`focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]\` +- **Disabled**: \`disabled:pointer-events-none disabled:opacity-50\` +- **Card spacing**: \`gap-6\` between cards, \`p-6\` internal padding +- **Section rhythm**: \`py-16\` between major sections +- **Form layout**: Vertical stack with \`gap-4\`, labels above inputs + +## Font Setup + +If fonts aren't loaded yet, add to your global CSS: +\`\`\`css +@font-face { font-family: 'Aspekta'; font-weight: 400; font-display: swap; src: url('/fonts/Aspekta-400.woff2') format('woff2'); } +@font-face { font-family: 'Aspekta'; font-weight: 500; font-display: swap; src: url('/fonts/Aspekta-500.woff2') format('woff2'); } +@font-face { font-family: 'Aspekta'; font-weight: 600; font-display: swap; src: url('/fonts/Aspekta-600.woff2') format('woff2'); } +@font-face { font-family: 'Aspekta'; font-weight: 700; font-display: swap; src: url('/fonts/Aspekta-700.woff2') format('woff2'); } +\`\`\` +` } // --------------------------------------------------------------------------- diff --git a/skill/AGENT.md b/skill/AGENT.md index 44b90bb..846a548 100644 --- a/skill/AGENT.md +++ b/skill/AGENT.md @@ -1,693 +1,53 @@ -# Greyhaven Design System +# Project Instructions -> **Auto-generated** by `scripts/generate-skill.ts` -- DO NOT EDIT by hand. +> **Auto-generated** by the Greyhaven Design System. > Re-generate: `pnpm skill:build` in the design system repo. > -> This file provides AI coding assistants (Cursor, GitHub Copilot, Windsurf, -> Codeium, etc.) with full context about the Greyhaven Design System. +> Copy this file to your project root as `AGENT.md`, `.cursorrules`, +> or `.github/copilot-instructions.md` depending on your AI tool. -## How to Use This +This project uses the **Greyhaven Design System**. -When building UI in this project, follow the Greyhaven Design System: -- Import components from `components/ui/` (or `@/components/ui/` with alias) -- Use semantic Tailwind classes (`bg-primary`, `text-foreground`, `border-border`) -- never raw hex colors -- Use `font-sans` (Aspekta) for UI elements, `font-serif` (Source Serif) for content -- Orange (`#D95E2A` / `bg-primary`) is the ONLY accent color -- use sparingly -- All components are framework-agnostic React (no Next.js imports) +## Rules ---- +- **ALWAYS use TypeScript** (`.tsx` / `.ts`). NEVER generate plain JavaScript (`.jsx` / `.js`). +- Use the `greyhaven` SKILL.md for full design system context (tokens, components, composition rules). It should be installed at `.claude/skills/greyhaven-design-system.md` or accessible to your AI tool. +- If the `greyhaven` MCP server is available, use its tools: + - `list_components()` to find the right component for a UI need + - `get_component(name)` to get exact props, variants, and usage examples + - `validate_colors(code)` to check code for off-brand colors + - `suggest_component(description)` to get recommendations +- Import components from `components/ui/` (or `@/components/ui/` with path alias) +- Never use raw hex colors -- use semantic Tailwind classes (`bg-primary`, `text-foreground`, `border-border`, etc.) +- Use `font-sans` (Aspekta) for UI elements: buttons, nav, labels, forms +- Use `font-serif` (Source Serif) for content: headings, body text +- Trust the design system's default component variants for accent -- they apply orange at the right scale. Don't apply `bg-primary` to large surfaces, containers, or section backgrounds +- All components are framework-agnostic React (no Next.js, no framework-specific imports) +- Dark mode is toggled via the `.dark` class -- use semantic tokens that adapt automatically -## Design Philosophy +## Component Summary -- **Minimal and restrained**: Off-white + off-black + warm greys. One single accent color (orange). No gradients, no decorative color, no multiple accent hues. -- **Typography-driven**: Source Serif 4/Pro (serif) for headings and body content. Aspekta (sans, self-hosted) for UI labels, buttons, navigation, and form elements. -- **Calm, professional aesthetic**: Tight border-radii, subtle shadows, generous whitespace. -- **Accessibility-first**: Built on Radix UI primitives for keyboard navigation, focus management, screen reader support. Visible focus rings, disabled states, ARIA attributes. -- **Dark mode native**: Thoughtful dark theme using inverted warm greys. Orange accent persists across both modes. Toggled via `.dark` class. -- **Framework-agnostic**: Pure React + Radix + Tailwind. No Next.js, no framework-specific imports. +37 components across 8 categories: primitives (10), layout (4), overlay (5), navigation (3), data (4), feedback (4), form (1), composition (6). ---- +For full component specs, props, and examples, refer to the SKILL.md file or use the MCP `get_component(name)` tool. + +## Key Patterns + +- **CVA variants**: Components use `class-variance-authority` for variant props +- **Slot composition**: Components use `data-slot="name"` attributes +- **Class merging**: Always use `cn()` from `@/lib/utils` (clsx + tailwind-merge) +- **Focus rings**: `focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]` +- **Disabled**: `disabled:pointer-events-none disabled:opacity-50` +- **Card spacing**: `gap-6` between cards, `p-6` internal padding +- **Section rhythm**: `py-16` between major sections +- **Form layout**: Vertical stack with `gap-4`, labels above inputs ## Font Setup -This design system uses two typefaces: - -| Role | Font | Usage | -|------|------|-------| -| **Sans (UI)** | Aspekta (self-hosted) | Buttons, nav, labels, forms, metadata | -| **Serif (Content)** | Source Serif 4/Pro | Headings, body text, reading content | - -### Aspekta (required) - -Aspekta font files live in `public/fonts/`. Add `@font-face` declarations to your global CSS: - +If fonts aren't loaded yet, add to your global CSS: ```css -/* Minimum set (covers font-weight 400-700) */ @font-face { font-family: 'Aspekta'; font-weight: 400; font-display: swap; src: url('/fonts/Aspekta-400.woff2') format('woff2'); } @font-face { font-family: 'Aspekta'; font-weight: 500; font-display: swap; src: url('/fonts/Aspekta-500.woff2') format('woff2'); } @font-face { font-family: 'Aspekta'; font-weight: 600; font-display: swap; src: url('/fonts/Aspekta-600.woff2') format('woff2'); } @font-face { font-family: 'Aspekta'; font-weight: 700; font-display: swap; src: url('/fonts/Aspekta-700.woff2') format('woff2'); } - -/* Or import all weights: */ -@import url('/fonts/font-face.css'); -``` - -### Font stack CSS variables - -```css ---font-sans: 'Aspekta', ui-sans-serif, system-ui, sans-serif; ---font-serif: 'Source Serif 4', 'Source Serif Pro', Georgia, serif; -``` - -### Tailwind usage - -- `font-sans` — Aspekta (UI elements) -- `font-serif` — Source Serif (content) - -Install fonts via: `./skill/install.sh /path/to/your/project` - ---- - -## Token Quick Reference - -Source of truth: `tokens/*.json` (W3C DTCG format). - -### Color - -| Token | Value | Description | -|-------|-------|-------------| -| `color.primitive.off-white` | `#F9F9F7` | Primary light surface — cards, elevated areas | -| `color.primitive.off-black` | `#161614` | Primary dark — foreground text, dark mode background | -| `color.primitive.orange` | `#D95E2A` | Only accent color — used sparingly for primary actions and emphasis | -| `color.primitive.destructive-red` | `#B43232` | Error/danger states | -| `color.primitive.grey.1` | `#F0F0EC` | 5% — Subtle backgrounds, secondary, muted | -| `color.primitive.grey.2` | `#DDDDD7` | 10% — Accent hover, light borders | -| `color.primitive.grey.3` | `#C4C4BD` | 20% — Border, input | -| `color.primitive.grey.4` | `#A6A69F` | 50% — Mid-tone | -| `color.primitive.grey.5` | `#7F7F79` | 60% — Mid-dark | -| `color.primitive.grey.7` | `#575753` | 70% — Secondary foreground, muted foreground | -| `color.primitive.grey.8` | `#2F2F2C` | 80% — Dark mode card, dark surfaces | -| `color.semantic.background` | `{color.primitive.grey.1}` | Page background | -| `color.semantic.foreground` | `{color.primitive.off-black}` | Primary text | -| `color.semantic.card` | `{color.primitive.off-white}` | Card/elevated surface background | -| `color.semantic.card-foreground` | `{color.primitive.off-black}` | Card text | -| `color.semantic.popover` | `{color.primitive.off-white}` | Popover background | -| `color.semantic.popover-foreground` | `{color.primitive.off-black}` | Popover text | -| `color.semantic.primary` | `{color.primitive.orange}` | Primary accent — buttons, links, focus rings | -| `color.semantic.primary-foreground` | `{color.primitive.off-white}` | Text on primary accent | -| `color.semantic.secondary` | `{color.primitive.grey.1}` | Secondary button/surface | -| `color.semantic.secondary-foreground` | `{color.primitive.grey.8}` | Text on secondary surface | -| `color.semantic.muted` | `{color.primitive.grey.1}` | Muted/subdued background | -| `color.semantic.muted-foreground` | `{color.primitive.grey.7}` | Muted/subdued text | -| `color.semantic.accent` | `{color.primitive.grey.2}` | Subtle hover state | -| `color.semantic.accent-foreground` | `{color.primitive.off-black}` | Text on accent hover | -| `color.semantic.destructive` | `{color.primitive.destructive-red}` | Destructive/error actions | -| `color.semantic.destructive-foreground` | `{color.primitive.off-white}` | Text on destructive | -| `color.semantic.border` | `{color.primitive.grey.3}` | Default border | -| `color.semantic.input` | `{color.primitive.grey.3}` | Input border | -| `color.semantic.ring` | `{color.primitive.orange}` | Focus ring | -| `color.semantic.chart.1` | `{color.primitive.orange}` | Chart accent | -| `color.semantic.chart.2` | `{color.primitive.grey.7}` | Chart secondary | -| `color.semantic.chart.3` | `{color.primitive.grey.5}` | Chart tertiary | -| `color.semantic.chart.4` | `{color.primitive.grey.4}` | Chart quaternary | -| `color.semantic.chart.5` | `{color.primitive.grey.8}` | Chart quinary | -| `color.semantic.sidebar.background` | `{color.primitive.grey.1}` | Sidebar background | -| `color.semantic.sidebar.foreground` | `{color.primitive.off-black}` | Sidebar text | -| `color.semantic.sidebar.primary` | `{color.primitive.orange}` | Sidebar primary accent | -| `color.semantic.sidebar.primary-foreground` | `{color.primitive.off-white}` | Sidebar primary text | -| `color.semantic.sidebar.accent` | `{color.primitive.grey.3}` | Sidebar accent/hover | -| `color.semantic.sidebar.accent-foreground` | `{color.primitive.off-black}` | Sidebar accent text | -| `color.semantic.sidebar.border` | `{color.primitive.grey.3}` | Sidebar border | -| `color.semantic.sidebar.ring` | `{color.primitive.orange}` | Sidebar focus ring | -| `color.dark.background` | `{color.primitive.off-black}` | Dark page background | -| `color.dark.foreground` | `{color.primitive.off-white}` | Dark primary text | -| `color.dark.card` | `{color.primitive.grey.8}` | Dark card surface | -| `color.dark.card-foreground` | `{color.primitive.off-white}` | Dark card text | -| `color.dark.popover` | `{color.primitive.grey.8}` | Dark popover | -| `color.dark.popover-foreground` | `{color.primitive.off-white}` | Dark popover text | -| `color.dark.primary` | `{color.primitive.orange}` | Same orange in dark mode | -| `color.dark.primary-foreground` | `{color.primitive.off-white}` | Dark primary foreground | -| `color.dark.secondary` | `{color.primitive.grey.7}` | Dark secondary | -| `color.dark.secondary-foreground` | `{color.primitive.off-white}` | Dark secondary text | -| `color.dark.muted` | `{color.primitive.grey.7}` | Dark muted | -| `color.dark.muted-foreground` | `{color.primitive.grey.3}` | Dark muted text | -| `color.dark.accent` | `{color.primitive.grey.7}` | Dark accent/hover | -| `color.dark.accent-foreground` | `{color.primitive.off-white}` | Dark accent text | -| `color.dark.destructive` | `{color.primitive.destructive-red}` | Same destructive in dark mode | -| `color.dark.destructive-foreground` | `{color.primitive.off-white}` | Dark destructive text | -| `color.dark.border` | `{color.primitive.grey.7}` | Dark border | -| `color.dark.input` | `{color.primitive.grey.7}` | Dark input border | -| `color.dark.ring` | `{color.primitive.orange}` | Dark focus ring | -| `color.dark.chart.1` | `{color.primitive.orange}` | Dark chart accent | -| `color.dark.chart.2` | `{color.primitive.grey.3}` | Dark chart secondary | -| `color.dark.chart.3` | `{color.primitive.grey.4}` | Dark chart tertiary | -| `color.dark.chart.4` | `{color.primitive.grey.5}` | Dark chart quaternary | -| `color.dark.chart.5` | `{color.primitive.grey.1}` | Dark chart quinary | -| `color.dark.sidebar.background` | `{color.primitive.grey.8}` | Dark sidebar background | -| `color.dark.sidebar.foreground` | `{color.primitive.off-white}` | Dark sidebar text | -| `color.dark.sidebar.primary` | `{color.primitive.orange}` | Dark sidebar primary | -| `color.dark.sidebar.primary-foreground` | `{color.primitive.off-white}` | Dark sidebar primary text | -| `color.dark.sidebar.accent` | `{color.primitive.grey.7}` | Dark sidebar accent | -| `color.dark.sidebar.accent-foreground` | `{color.primitive.off-white}` | Dark sidebar accent text | -| `color.dark.sidebar.border` | `{color.primitive.grey.7}` | Dark sidebar border | -| `color.dark.sidebar.ring` | `{color.primitive.orange}` | Dark sidebar ring | - -### Typography - -| Token | Value | Description | -|-------|-------|-------------| -| `typography.fontFamily.sans` | `["Aspekta","ui-sans-serif","system-ui","sans-serif"]` | UI labels, buttons, nav, forms — Aspekta self-hosted | -| `typography.fontFamily.serif` | `["Source Serif 4","Source Serif Pro","Georgia","serif"]` | Headings, body content, reading — Source Serif primary | -| `typography.fontFamily.mono` | `["ui-monospace","SFMono-Regular","Menlo","Monaco","Consolas","monospace"]` | Code blocks and monospaced content | -| `typography.fontSize.xs` | `0.75rem` | 12px — metadata, fine print | -| `typography.fontSize.sm` | `0.875rem` | 14px — captions, nav, labels, buttons | -| `typography.fontSize.base` | `1rem` | 16px — body text | -| `typography.fontSize.lg` | `1.125rem` | 18px — large body, subtitles | -| `typography.fontSize.xl` | `1.25rem` | 20px — H3 | -| `typography.fontSize.2xl` | `1.5rem` | 24px — H2 | -| `typography.fontSize.3xl` | `1.875rem` | 30px — large H2 | -| `typography.fontSize.4xl` | `2.25rem` | 36px — H1 | -| `typography.fontSize.5xl` | `3rem` | 48px — hero heading | -| `typography.fontWeight.normal` | `400` | Regular body text | -| `typography.fontWeight.medium` | `500` | H3, labels, nav items | -| `typography.fontWeight.semibold` | `600` | H1, H2, buttons | -| `typography.fontWeight.bold` | `700` | Strong emphasis | -| `typography.lineHeight.tight` | `1.25` | Headings | -| `typography.lineHeight.normal` | `1.5` | Default | -| `typography.lineHeight.relaxed` | `1.625` | Body content for readability | -| `typography.letterSpacing.tight` | `-0.025em` | Headings — tracking-tight | -| `typography.letterSpacing.normal` | `0em` | Body text | -| `typography.letterSpacing.wide` | `0.05em` | Uppercase labels | - -### Spacing - -| Token | Value | Description | -|-------|-------|-------------| -| `spacing.0` | `0` | None | -| `spacing.1` | `0.25rem` | 4px — tight gaps | -| `spacing.2` | `0.5rem` | 8px — card header gap, form description spacing | -| `spacing.3` | `0.75rem` | 12px | -| `spacing.4` | `1rem` | 16px — form field gap, button padding | -| `spacing.5` | `1.25rem` | 20px | -| `spacing.6` | `1.5rem` | 24px — card padding, card internal gap | -| `spacing.8` | `2rem` | 32px — section margin-bottom | -| `spacing.10` | `2.5rem` | 40px | -| `spacing.12` | `3rem` | 48px | -| `spacing.16` | `4rem` | 64px — major section padding (py-16) | -| `spacing.20` | `5rem` | 80px | -| `spacing.24` | `6rem` | 96px — hero padding | -| `spacing.0.5` | `0.125rem` | 2px — micro spacing | -| `spacing.1.5` | `0.375rem` | 6px | -| `spacing.component.card-padding` | `1.5rem` | Card internal padding (px-6) | -| `spacing.component.card-gap` | `1.5rem` | Gap between cards (gap-6) | -| `spacing.component.section-padding` | `4rem` | Vertical padding between major sections (py-16) | -| `spacing.component.form-gap` | `1rem` | Gap between form fields (gap-4) | -| `spacing.component.button-padding-x` | `1rem` | Button horizontal padding (px-4) | -| `spacing.component.navbar-height` | `4rem` | Navbar height (h-16) | - -### Radii - -| Token | Value | Description | -|-------|-------|-------------| -| `radii.base` | `0.375rem` | 6px — base radius | -| `radii.sm` | `calc(0.375rem - 2px)` | 4px — small variant | -| `radii.md` | `0.375rem` | 6px — medium (same as base) | -| `radii.lg` | `calc(0.375rem + 2px)` | 8px — large variant | -| `radii.xl` | `calc(0.375rem + 4px)` | 10px — extra large variant (cards) | -| `radii.full` | `9999px` | Fully round (pills, avatars) | - -### Shadows - -| Token | Value | Description | -|-------|-------|-------------| -| `shadow.xs` | `{"offsetX":"0","offsetY":"1px","blur":"2px","spread":"0","color":"rgba(22, 22, 20, 0.05)"}` | Subtle shadow for buttons, inputs | -| `shadow.sm` | `{"offsetX":"0","offsetY":"1px","blur":"3px","spread":"0","color":"rgba(22, 22, 20, 0.1)"}` | Small shadow for cards | -| `shadow.md` | `{"offsetX":"0","offsetY":"4px","blur":"6px","spread":"-1px","color":"rgba(22, 22, 20, 0.1)"}` | Medium shadow for dropdowns, popovers | -| `shadow.lg` | `{"offsetX":"0","offsetY":"10px","blur":"15px","spread":"-3px","color":"rgba(22, 22, 20, 0.1)"}` | Large shadow for dialogs, modals | - -### Motion - -| Token | Value | Description | -|-------|-------|-------------| -| `motion.duration.fast` | `150ms` | Quick transitions — tooltips, hover states | -| `motion.duration.normal` | `200ms` | Default transitions — most UI interactions | -| `motion.duration.slow` | `300ms` | Deliberate transitions — modals, drawers, accordions | -| `motion.easing.default` | `[0.4,0,0.2,1]` | Standard ease-in-out | -| `motion.easing.in` | `[0.4,0,1,1]` | Ease-in for exits | -| `motion.easing.out` | `[0,0,0.2,1]` | Ease-out for entrances | - ---- - -## Component Catalog (37 components) - -All components live in `components/ui/`. Import with `@/components/ui/`. - -### Primitives - -#### Button -- **File**: `components/ui/button.tsx` -- **Exports**: `Button`, `buttonVariants` -- **Description**: Primary interactive element. 6 variants: default (orange), secondary, outline, ghost, link, destructive. Sizes: default (h-9), sm (h-8), lg (h-10), icon (size-9). -- **Props**: `variant?: "default" | "secondary" | "outline" | "ghost" | "link" | "destructive"; size?: "default" | "sm" | "lg" | "icon" | "icon-sm" | "icon-lg"; asChild?: boolean` -- **Example**: -```tsx - -``` - -#### Badge -- **File**: `components/ui/badge.tsx` -- **Exports**: `Badge`, `badgeVariants` -- **Description**: Status indicator / tag. Variants include default, secondary, outline, destructive, success, warning, info, plus channel pills (WhatsApp, Email, Telegram, Zulip). -- **Props**: `variant?: "default" | "secondary" | "destructive" | "outline" | "success" | "warning" | "info" | ...; asChild?: boolean` -- **Example**: -```tsx -Active -``` - -#### Input -- **File**: `components/ui/input.tsx` -- **Exports**: `Input` -- **Description**: Text input field with focus ring, disabled, and aria-invalid states. -- **Props**: `All standard HTML input props` -- **Example**: -```tsx - -``` - -#### Textarea -- **File**: `components/ui/textarea.tsx` -- **Exports**: `Textarea` -- **Description**: Multi-line text input. -- **Props**: `All standard HTML textarea props` -- **Example**: -```tsx -