apps/shade/src/docs/contributing.mdx
import { Meta } from '@storybook/addon-docs/blocks';
<Meta title="Overview / Contributing" /> <div className="sb-doc">apps/shade/src/
├── components/
│ ├── ui/ Generic controls + recipes
│ ├── primitives/ Layout primitives (Stack, Inline, …)
│ ├── patterns/ Product compositions (PageHeader, ListPage, …)
│ └── posts-stats/ Interim shared between Posts and Stats
├── docs/ MDX + showcase stories rendered in Storybook
├── hooks/ Generic React hooks
├── lib/ Utilities (cn, formatters, chart helpers)
└── providers/ Context providers
Each entrypoint barrel (components.ts, primitives.ts, patterns.ts) re-exports from its folder.
| What | Convention | Example |
|---|---|---|
| File names | kebab-case | dropdown-menu.tsx |
| Component identifiers | PascalCase | DropdownMenu |
| Hooks, functions, variables | camelCase | useFocusTrap, formatNumber |
| Storybook titles | layer prefix | Components / Button, Patterns / PageHeader, Recipes / inputSurface |
Always forward and merge className with cn(...). Use cva() for variants.
Always use the layer-specific subpath:
import {Button} from '@tryghost/shade/components';
import {Stack} from '@tryghost/shade/primitives';
import {PageHeader} from '@tryghost/shade/patterns';
import {cn} from '@tryghost/shade/utils';
Inside Shade itself, use the @/ alias for cross-file imports.
Every component ships with <name>.stories.tsx next to it:
title follows the layer convention (table above).tags: ['autodocs'] so docs render.parameters.docs.description.component.parameters.docs.description.story explaining when to use it.Stories are the gallery. Many small focused stories beat one prose-heavy story.
Most new components start from a ShadCN install:
pnpm dlx shadcn@latest add <component-name>
Guardrails:
bg-background, text-foreground, border-border-default, --surface-elevated). Never hard-code hex values.dark: variants for colour — semantic tokens flip automatically. Exceptions: assets like logos and illustrations.apps/shade/theme-variables.css (semantic) or apps/shade/tailwind.theme.css (raw @theme). Don't introduce ad-hoc CSS variables in component files.Before merging a component:
className forwarded and merged with cn()bg-gray-200-style raw utilities for UI chromepnpm lint, pnpm test, and Storybook all cleanCommit message format (full rules in apps/shade/AGENTS.md):
Added Avatar component
ref https://linear.app/ghost/issue/DES-1234/avatar
Builds on Radix Avatar with a size variant scale and a fallback initials slot.
ref, closes, fixes) + full Linear URL.PRs: screenshots or GIFs for any UI change; link the Linear issue; update or add stories.
</div>