Github
Authoring

Catalog overview

How Handoff registers catalog items, why ids matter, and which entry point to import from.

Every documented UI entry is a catalog item. An item lives in a declaration file next to the code it documents, and it declares exactly one of these:

  • An implementation, which names a renderer: React, Handlebars, or a Storybook CSF story file. The registry stores the item as a component. See Catalog items.
  • A composition of other items. The registry stores the item as a pattern. See Compositions.

An item never declares both.

Pick the entry point

defineCatalogItem comes from the module of the renderer. The module you import from is the only place Handoff learns the renderer; it never guesses from a file name.

ItemImport
React componentimport { defineCatalogItem } from 'handoff-app/react'
React component from a CSF story fileimport { defineCatalogItem, fromCSF } from 'handoff-app/react'
Handlebars templateimport { defineCatalogItem } from 'handoff-app/handlebars'
Compositionimport { defineCatalogItem } from 'handoff-app/pattern'

Declaration files

  • Name declarations <name>.handoff.ts, .handoff.tsx, .handoff.js, or .handoff.cjs.
  • A declaration that contains JSX must use .handoff.tsx.
  • The default export is the item. Each other named export is a preview.
  • JSON declarations are not supported.

Registration

catalog.include in the config registers the catalog directories. Each path is either an item directory or a collection directory whose subdirectories are items:

handoff.config.ts
import { defineConfig } from 'handoff-app'

export default defineConfig({
  catalog: { include: ['components', 'patterns'] },
})

npx handoff-app make:component <name> creates a Handlebars item in the first catalog.include directory (or components/) and registers that directory when needed.

Ids

The id addresses an item everywhere: its build artifacts, its documentation URL, MCP, and the publish and checkout commands. Keep ids stable.

  • Components and patterns share one namespace, so each id must be unique across both.
  • If two declarations use one id, Handoff keeps the first, skips the second, and names both files in a warning.
  • publish does not run until every id is unique.

Metadata

Every item takes the same documentation fields:

FieldPurpose
idStable id
nameDisplay name
descriptionUsage guidance
groupNavigation group, for example Forms
typeClassification. Your editor suggests element and block, and any other value works.
categories, tagsFree-form classification. MCP search filters on both.
shouldDo, shouldNotDoBest practices and common mistakes, shown on the component page
entriesSupporting files: scss, js, schema, templates

The type also sets how previews display: element previews (the default) are centered on a transparent background, and block previews take the full width.

Validate

Terminal
npx handoff-app validate:components

Builds every item in validation mode, which runs the validateComponent hook. Without the hook, the command only builds.

On this page