Sitefold Design

Contributing

Every shared component belongs in the library. Adding one is three steps.

Adding a component

  1. Build it in src/components/ui/<Name>/ — component, CSS file and an index.ts — or src/components/layout/ for shell pieces.
  2. Add an entry to the right group in src/app/library/library-nav.ts. That one file drives the sidebar, the search index, the catalogue card, the static routes and the prev/next pager.
  3. Write its doc in src/app/library/content/<group>.tsx and add it to the array that file exports.

The nav entry alone is enough to make the component appear — a component listed but not yet documented gets a page saying so, rather than a 404. That is deliberate: the gap is visible instead of silent.

Writing the page

A doc is data — a summary, some sections, and a props table:

const badge: ComponentDoc = {
  slug: "badge",
  importPath: "@/components/ui/Badge",
  source: "src/components/ui/Badge",
  usage: <p>When to reach for this rather than something else.</p>,
  sections: [
    {
      id: "tones",
      title: "Tones",
      body: <p>What the reader needs to know.</p>,
      examples: [{ code: "<Badge>Draft</Badge>", render: <Badge>Draft</Badge> }],
    },
  ],
  props: [{ name: "Badge", rows: [/* … */] }],
  related: ["alert"],
};

Three things are worth getting right:

  • code and render must agree. They sit next to each other in the same object so a mismatch is visible in review — the sample is not generated from the render, so nothing else will catch it.
  • Lead with the most representative example.The first example of the first section becomes the catalogue card's live preview.
  • Transcribe props from the source, including defaults and aliases. Types are written as they appear in the file so a reader can grep for them.

Examples that need state get a small demo component in the same file — keep it above the doc object so the doc stays readable.

Verifying

Before claiming it works:

npm run design:audit   # tiers, resolution, contrast, banned deps
npm test
npx tsc --noEmit
npm run lint

The audit exits non-zero at a known raw-value baseline — that is not a regression. What must stay at zero is token-resolution, tier, contrast, banned-dependency, banned-config and stale-allowlist findings. See Working with tokens.

Then look at it, on this site and on the surface it will actually be used on. Check it on more than one accent if it uses accent colour at all — the inheritance trap is only visible by measurement.