{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "charles", "title": "Design System", "description": "shincn design system document — drop in any project root for AI-assisted UI generation", "files": [ { "path": "charles.md", "content": "# charles.md `v2.0`\n\n> Drop this file in any project root. Claude Code will read it and produce UI that matches this design system, aesthetic, and decision-making process exactly.\n\n---\n\n## Who This Is For\n\nThis is the personal design system document for ch.sh. It encodes visual style, component philosophy, motion principles, and explicit anti-patterns. When building any UI in this project, treat this file as ground truth.\n\n---\n\n## Stack (Non-Negotiable)\n\n| Layer | Library |\n|---|---|\n| Framework | React |\n| Styling | Tailwind CSS |\n| Components | shadcn/ui (Radix primitives under the hood) |\n| Animation | Framer Motion |\n| Icons | Phosphor Icons (`@phosphor-icons/react`) |\n| Utility | `clsx` + `tailwind-merge` via `cn()` |\n\nNever suggest replacing these. Never use Lucide, Heroicons, or other icon sets. Never use CSS-in-JS, styled-components, or Emotion.\n\n---\n\n## Pre-Build Protocol (Before Writing Any Code)\n\nThe biggest failure mode in AI-generated UI is skipping the aesthetic decision and jumping straight to code. The result is distributional convergence — predictable fonts, gradient fills on white, soft card shadows, standard grid layouts. Statistically safe and visually invisible.\n\nBefore building any component or screen, answer these four questions first:\n\n1. **What problem does this interface solve, and who uses it?** The answer should change every visual decision downstream.\n2. **What's the tonal register?** Pick a clear position: utilitarian and dense, warm and approachable, minimal and precise, editorial, etc. Don't land in the middle — the middle is where generic lives.\n3. **What's the one spatial idea?** Every good screen has a compositional thesis — a dominant element, a clear reading order, an asymmetry that creates tension. Name it before you build it.\n4. **What would the wrong version look like?** Articulate the AI default for this specific UI, then do the opposite.\n\nOnly after answering these should code be written.\n\n---\n\n## Typography\n\n### Font Stack\n\n```css\n/* Default — everywhere */\nfont-family: \"SF Pro Rounded\", -apple-system, BlinkMacSystemFont, sans-serif;\n\n/* Monospace — code snippets, addresses, hashes, technical strings only */\nfont-family: \"SF Mono\", ui-monospace, monospace;\n```\n\nSF Pro Rounded is the only sans-serif. Every heading, label, body text, button, and caption uses it. There is no secondary sans-serif option. Use SF Mono exclusively for code snippets, wallet addresses, hashes, CLI output, or numeric strings that need tabular alignment.\n\n### Type Scale\n\nUse Tailwind's default type scale. Avoid custom font sizes unless absolutely necessary.\n\n**Hierarchy rules:**\n- Page titles: `text-2xl font-semibold` or `text-3xl font-semibold`\n- Section headings: `text-lg font-medium` or `text-xl font-semibold`\n- Body: `text-sm` or `text-base` — never go below `text-xs` for readable content\n- Labels, captions, helper text: `text-xs text-muted-foreground`\n- Never use `font-bold` for UI labels — `font-medium` or `font-semibold` only\n- Never change font weight on hover/selected states — it causes layout shift\n- Use `font-variant-numeric: tabular-nums` for any column of numbers\n\n---\n\n## Color Philosophy\n\nLight-first always. No dark mode unless explicitly requested.\n\n**Neutral base + brand color system.** Colors are functional, not decorative. Every use of color should communicate something — status, hierarchy, interactivity, or brand.\n\n**Palette approach:**\n- Base: white/zinc/slate neutrals via Tailwind + shadcn tokens\n- One primary brand accent per project, applied sparingly\n- Use `text-muted-foreground` for secondary text, not custom grays\n- Borders: `border` (default shadcn token) — never hand-roll border colors\n- Destructive states: use shadcn's `destructive` token, not custom red\n\n**Color anti-patterns:**\n- Never use color purely for decoration (gradient backgrounds on cards \"just because\")\n- Never use 3+ accent colors in the same UI\n- Never use color to convey the only distinction — always pair with shape or label\n\n---\n\n## Spacing & Density\n\nBalanced — comfortable but not wasteful. The goal is breathing room with purpose.\n\n**Mental model:** Every gap should feel intentional. If you can remove 4px and nothing feels cramped, remove it. If something feels tight, add 4px before adding 8px.\n\n### Spacing Table\n\n| Context | Value | Tailwind |\n|---|---|---|\n| Between related items in a group | 4–8px | `gap-1` to `gap-2` |\n| Between sections or card internals | 12–16px | `gap-3` to `gap-4` |\n| Page section spacing | 24–32px | `gap-6` to `gap-8` |\n| Page container padding | 16px | `px-4` |\n| Card internal padding | 16–20px | `p-4` to `p-5` |\n| Button padding | `px-4 py-2` (default), `px-3 py-1.5` (small) | — |\n\n### Container Defaults\n\n```tsx\n// Default page container\n
\n// or max-w-3xl for wider content — never go above max-w-3xl for text\n```\n\n---\n\n## Border Radius\n\nRestrained and consistent. Radius values should feel quiet, not bubbly.\n\n| Element | Value | Tailwind |\n|---|---|---|\n| Buttons, inputs, select | 6px | `rounded-md` |\n| Cards, panels | 8px | `rounded-lg` |\n| Modals, dialogs | 12px | `rounded-xl` |\n| Avatars, status dots | 50% | `rounded-full` |\n\nNever use `rounded-2xl` or `rounded-3xl` on buttons or inputs. Never use `rounded-full` on rectangular cards. Don't give every element in the UI the same radius value — the variation between `md`, `lg`, and `xl` creates subtle hierarchy.\n\n---\n\n## Components\n\n### Buttons\n\nUse shadcn `Button` variants. Don't create custom button components.\n\n```tsx\n// Primary action\n\n\n// Secondary\n\n\n// Destructive — always behind a confirmation dialog\n\n\n// Ghost — for toolbar-style actions\n\n```\n\n**Button rules:**\n- One primary button per view section — if everything is primary, nothing is\n- Icon-only buttons always need `aria-label`\n- Destructive actions always need a confirmation Dialog\n- Loading state: disable the button and show a spinner — prevent double submissions\n\n### Inputs\n\n```tsx\n// Always wrap in a proper label\n
\n \n \n
\n```\n\n- Input font size must be ≥ 16px to prevent iOS Safari zoom on focus\n- Placeholder text is not a label — ever\n- Error messages go directly below the input, not in a toast\n\n### Modals / Dialogs\n\n- Use shadcn `Dialog` — Radix-based, accessible by default\n- Autofocus first input on open (desktop only)\n- Confirm before any destructive action — use a proper Dialog, not `window.confirm()`\n- Modal + overlay: same easing, same duration\n\n---\n\n## Motion (Emil Kowalski Principles)\n\nMotion should feel native, not performative. Product UI is fast and purposeful. Marketing pages can be more expressive.\n\n### Easing Blueprint\n\n```css\n/* Elements entering or exiting → ease-out */\n--ease-out-cubic: cubic-bezier(0.215, 0.61, 0.355, 1);\n--ease-out-quart: cubic-bezier(0.165, 0.84, 0.44, 1);\n\n/* Elements moving while on screen → ease-in-out */\n--ease-in-out-cubic: cubic-bezier(0.645, 0.045, 0.355, 1);\n\n/* Hover / color transitions → ease */\ntransition: background-color 150ms ease;\n```\n\n### Duration Guidelines\n\n| Element | Duration |\n|---|---|\n| Micro-interactions (hover, color) | 100–150ms |\n| Dropdowns, tooltips | 150–200ms |\n| Modals, drawers | 200–300ms |\n| Page transitions | 300–400ms max |\n\n**Frequency rule:** If a user will see this 100+ times a day, don't animate it. Raycast doesn't animate its menu toggle. Follow that instinct.\n\n### Framer Motion Defaults\n\n```tsx\n// Enter/exit pattern\n\n\n// Spring — for drag and playful interactions only\n{ type: \"spring\", duration: 0.5, bounce: 0.15 }\n\n// Reduced motion — always\nconst shouldReduceMotion = useReducedMotion();\ninitial={shouldReduceMotion ? false : { opacity: 0, y: 8 }}\n```\n\n### Motion Rules\n\n1. Only animate `transform` and `opacity` — these run on the GPU and skip layout/paint\n2. Never animate `height`, `width`, `padding`, or `margin` directly\n3. Paired elements (modal + overlay, tooltip + arrow) must share the same easing and duration\n4. Every animation needs `prefers-reduced-motion` support\n5. Use `AnimatePresence` with `mode=\"wait\"` for route transitions\n6. Don't use `transition: all` — specify exact properties\n\n---\n\n## Layout\n\n- `max-w-2xl` to `max-w-3xl` for content pages — never let text go full width\n- Left-align content by default — centered layouts only for empty states and marketing heroes\n- Use CSS Grid for two-dimensional layouts, Flexbox for one-dimensional\n- Sidebar + main: prefer fixed sidebar width (`w-64` or `w-72`) + `flex-1` main\n- Sticky headers: `sticky top-0 z-10 bg-background/80 backdrop-blur-sm`\n\n**Spatial composition — what most AI UI skips:**\nEvery screen should have a compositional thesis — a dominant element that draws the eye first, a clear reading hierarchy, and intentional use of negative space. Don't arrange elements in a grid just because it's easy; arrange them based on importance and flow. Generous negative space OR controlled density — pick one and commit. The middle is mud.\n\n---\n\n## Interactive States (All Required)\n\n| State | Required |\n|---|---|\n| Default | ✓ |\n| Hover | ✓ — `@media (hover: hover)` only |\n| Focus-visible | ✓ — never remove, always style |\n| Active / pressed | ✓ — `scale(0.97)` on buttons |\n| Disabled | ✓ — `opacity-50 cursor-not-allowed pointer-events-none` |\n| Loading | ✓ — spinner or skeleton, button disabled |\n| Error | ✓ — inline message below the field |\n| Empty state | ✓ — don't leave blank space, write helpful copy |\n\nAI-generated UI almost always skips active, error, and empty states. These are non-negotiable.\n\n---\n\n## Anti-Patterns (Never Do These)\n\nThese are the things that make me immediately want to rewrite output:\n\n**Radius crimes:**\n- `rounded-2xl` or `rounded-3xl` on buttons or inputs\n- `rounded-full` on rectangular cards\n- Everything in the UI having the same radius value\n\n**Shadow abuse:**\n- `shadow-xl` or `shadow-2xl` on standard cards\n- Shadows AND borders on the same element\n- Inner shadows for no reason\n\n**Layout crimes:**\n- Content area `max-w-7xl` or wider — text lines become unreadable\n- Everything centered, nothing left-aligned\n- Padding applied at every nesting level instead of at the container\n\n**Generic SaaS patterns:**\n- Hero sections with a headline, subheadline, two CTA buttons, and a screenshot — rethink the structure\n- Feature grids with icons and 3-word labels\n- Pricing tables that look like every other pricing table\n- `bg-gradient-to-br from-purple-500 to-blue-500` as a section background\n\n**Color as decoration:**\n- Gradient fills on cards just to add visual interest\n- Colorful icon backgrounds (the `bg-blue-100 rounded-lg p-2` icon wrapper pattern)\n- Using 3+ accent colors in the same view\n\n**Typography mistakes:**\n- `font-bold` on UI labels (use `font-medium` or `font-semibold`)\n- All caps labels for everything\n- Font weight changing on hover — causes layout shift\n- Line length over ~70 characters for body text\n\n**Motion mistakes:**\n- `transition: all` — always specify properties\n- Scroll-triggered fade-ups on every section\n- Animations on frequently-used UI (tabs, nav items, toggles)\n- No `prefers-reduced-motion` support\n\n---\n\n## Accessibility (WCAG 2.1 AA — Non-Negotiable)\n\nIssues are ranked CRITICAL → SERIOUS → MODERATE. Fix in that order.\n\n### CRITICAL — Ship blockers\n\n**Missing accessible names (WCAG 4.1.2)**\nEvery icon-only button needs `aria-label`. No exceptions.\n```tsx\n// Wrong\n\n\n// Right\n\n```\n\n**Color contrast — text (WCAG 1.4.3)**\nNormal text: 4.5:1 minimum. Large text (18px+ or 14px+ bold): 3:1 minimum. If shadcn tokens are used correctly, this is handled. The moment you hand-roll a color, verify contrast.\n\n**Color as the only differentiator (WCAG 1.4.1)**\nNever use color alone to communicate state, error, or category. Always pair color with an icon, label, or pattern.\n\n**Form inputs without labels (WCAG 1.3.1)**\nEvery input must have an associated label via `htmlFor` or wrapping element. Placeholder text is not a label.\n\n### SERIOUS — Fix before review\n\n**Focus outline removed (WCAG 2.4.7)**\n`outline-none` without a replacement focus style is a blocking accessibility failure.\n```tsx\n// Wrong — destroys keyboard navigation\nclassName=\"focus:outline-none\"\n\n// Right — custom focus ring that matches the design\nclassName=\"focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2\"\n```\n\n**Keyboard navigation broken**\nTab order must follow visual reading order. Every interactive element must be reachable and operable by keyboard.\n\n### MODERATE — Polish pass\n\n**Missing `aria-live` for dynamic content**\nToast notifications, status updates, and async results need `role=\"status\"` or `aria-live=\"polite\"`.\n\n**`prefers-reduced-motion` not respected**\nEvery animation must have a reduced-motion alternative. Including opacity transitions.\n\n**iOS zoom on input focus**\nInput font size must be ≥ 16px or iOS Safari will zoom the viewport on focus.\n\n---\n\n## Design Decision Framework\n\nWhen Claude Code faces an ambiguous design decision, apply this hierarchy:\n\n1. **Does shadcn already have this component?** Use it. Don't reinvent.\n2. **Does the pattern exist in the project already?** Match it for consistency.\n3. **Is this a product UI or a marketing surface?** Product = fast and minimal. Marketing = can be more expressive.\n4. **Will the user see this 100+ times?** If yes, make it invisible — no animation, low visual weight.\n5. **Is this the primary action?** If yes, it gets visual weight. If no, it's ghost/outline/muted.\n6. **When in doubt, do less.** Remove the shadow. Remove the gradient. Remove the extra color. Restraint is the design.\n\n---\n\n## Design Review Protocol\n\nWhen reviewing any UI output, run these passes in order:\n\n**Pass 1 — CRITICAL (accessibility failures)**\n- [ ] Any `