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
compositionof 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.
| Item | Import |
|---|---|
| React component | import { defineCatalogItem } from 'handoff-app/react' |
| React component from a CSF story file | import { defineCatalogItem, fromCSF } from 'handoff-app/react' |
| Handlebars template | import { defineCatalogItem } from 'handoff-app/handlebars' |
| Composition | import { 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:
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.
publishdoes not run until every id is unique.
Metadata
Every item takes the same documentation fields:
| Field | Purpose |
|---|---|
id | Stable id |
name | Display name |
description | Usage guidance |
group | Navigation group, for example Forms |
type | Classification. Your editor suggests element and block, and any other value works. |
categories, tags | Free-form classification. MCP search filters on both. |
shouldDo, shouldNotDo | Best practices and common mistakes, shown on the component page |
entries | Supporting 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
npx handoff-app validate:componentsBuilds every item in validation mode, which runs the validateComponent
hook. Without the hook, the command only builds.