Back to Ghost

Components

apps/shade/src/docs/component-contracts.mdx

6.38.01.7 KB
Original Source

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

<Meta title="Components / Components Guide" /> <div className="sb-doc">

Components

<p className="excerpt">Generic, reusable UI controls — `Button`, `Input`, `Dialog`, `Tabs`, `Card`, and so on. Same on every page, no Ghost-specific knowledge.</p>

What makes a component "done"

  • Works in all four states: default, hover, focus-visible, disabled.
  • Drives appearance through semantic tokens (bg-background, border-border-default, ring-focus-ring) — no hex values, no bg-gray-200-style raw palette utilities.
  • Props express visual or interactive intent (variant, size, loading), not workflow (isMembersPage, layoutMode).
  • Forwards className and merges with cn(...).
  • Has stories covering variants and states.

If a component starts collecting workflow-specific props, stop and extract a Pattern — don't grow the API.

Use a component when

  • The control is generic enough to live on any page (Members, Settings, Stats, the post editor).
  • The name sounds like the open web, not Ghost. Button, Dialog, Tabs — yes. MembersList, PostHeader — no, that's a Pattern.

Browse the sidebar for the live inventory and APIs. Full rules: Layers. Agent rules: apps/shade/AGENTS.md.

Example: good vs. bad props

tsx
// Good — visual intent, works in any context
<Button loading={isPending} size="sm" variant="destructive">Delete</Button>

// Bad — leaks product knowledge into a generic control
<Button isMembersPage layoutMode="toolbarWithFilterAndStats">Add</Button>

When you catch yourself reaching for the second kind, that's a Pattern trying to hide as a Button.

</div>