Back to Ghost

Contributing

apps/shade/src/docs/contributing.mdx

6.38.04.3 KB
Original Source

import { Meta } from '@storybook/addon-docs/blocks';

<Meta title="Overview / Contributing" /> <div className="sb-doc">

Contributing

<p className="excerpt">Conventions for adding to Shade. The decision tree for *which layer* something belongs in is on the [Layers](?path=/docs/overview-layers--docs) page. The agent-facing rule source is `apps/shade/AGENTS.md`.</p>

File layout

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.

Naming

WhatConventionExample
File nameskebab-casedropdown-menu.tsx
Component identifiersPascalCaseDropdownMenu
Hooks, functions, variablescamelCaseuseFocusTrap, formatNumber
Storybook titleslayer prefixComponents / Button, Patterns / PageHeader, Recipes / inputSurface

Always forward and merge className with cn(...). Use cva() for variants.

Imports

Always use the layer-specific subpath:

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

Stories

Every component ships with <name>.stories.tsx next to it:

  • title follows the layer convention (table above).
  • tags: ['autodocs'] so docs render.
  • A short parameters.docs.description.component.
  • One story per important variant/state, each with a one-line parameters.docs.description.story explaining when to use it.

Stories are the gallery. Many small focused stories beat one prose-heavy story.

ShadCN

Most new components start from a ShadCN install:

bash
pnpm dlx shadcn@latest add <component-name>

Guardrails:

  • Never overwrite an existing Shade component when the CLI prompts. Choose "No".
  • Run on a fresh branch so the CLI's diff is clean.
  • If the component already exists, generate into a scratch repo and port the parts you actually want.
  • After integrating: replace any raw colour/spacing with semantic tokens; ensure default / hover / focus-visible / disabled all work; trim props that hint at a specific surface.

Tokens & dark mode

  • Reference semantic tokens (bg-background, text-foreground, border-border-default, --surface-elevated). Never hard-code hex values.
  • Don't write dark: variants for colour — semantic tokens flip automatically. Exceptions: assets like logos and illustrations.
  • New tokens go in apps/shade/theme-variables.css (semantic) or apps/shade/tailwind.theme.css (raw @theme). Don't introduce ad-hoc CSS variables in component files.

Acceptance checklist

Before merging a component:

  • Lives in the right layer (see Layers)
  • className forwarded and merged with cn()
  • All states work: default, hover, focus-visible, disabled
  • Semantic tokens only; no hex values, no bg-gray-200-style raw utilities for UI chrome
  • Story covers variants + states with one-line "when to use" descriptions
  • No product-specific props on a generic control
  • pnpm lint, pnpm test, and Storybook all clean

Commits & PRs

Commit 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.
  • 1st line: ≤ 80 chars, past tense, starts with one of: Fixed / Changed / Updated / Improved / Added / Removed / Reverted / Moved / Released / Bumped / Cleaned.
  • 3rd line: magic word (ref, closes, fixes) + full Linear URL.
  • 4th+: the why, not the what.

PRs: screenshots or GIFs for any UI change; link the Linear issue; update or add stories.

</div>