Guides
Designing an Expandable Component Catalog
Keep real UI implementations separate from their catalog data so new components join one dependable presentation path.
A guide to the component catalog already used by WebCraft: its source manifests, local adapter, registry, standardized model, and static previews.
Separate the UI from the catalog
components/ui contains the real Button, Badge, Card, and Container implementations. The component catalog is a separate business module that describes those verified components for people who need to choose and understand them.
This distinction keeps catalog prose, categories, limitations, and routes from leaking into the reusable UI primitives. It also prevents a catalog page from inventing an API that the real component does not provide.
Standardize source data before pages
Each component begins as a ComponentManifest: trusted, hand-maintained source facts such as its name, capabilities, accessibility notes, and source path. A standard Component is the normalized result that pages and catalog business components consume.
The boundary matters even when the two shapes are similar. Future data formats can receive their own adapter, while the list, detail page, metadata, and previews continue to depend on one stable Component model.
Registry built through the local adapter
The current registry converts each independent manifest before exposing the catalog collection.
export const components = [
adaptLocalComponent(buttonManifest),
adaptLocalComponent(badgeManifest),
adaptLocalComponent(cardManifest),
adaptLocalComponent(containerManifest),
] as const satisfies readonly Component[];Use an adapter and registry
The Local Adapter is a pure function. It copies scalar fields and clones technologies, capabilities, and limitations so a page never receives a mutable reference to the manifest source.
The Registry collects normalized components in one place. Query helpers return copied results by slug, category, or featured state, which keeps read-only catalog data from becoming accidental page state.
Slug query
Pages use a normalized query instead of reading a manifest directly.
export function getComponentBySlug(slug: string): Component | undefined {
const component = components.find((entry) => entry.slug === slug);
return component ? copyComponent(component) : undefined;
}Preview real components
A catalog preview imports the existing UI component and renders a restrained static example. Button previews use its real variants and states; Card previews use its real materials; no generated markup or executable example is introduced.
Reusing the real component prevents the documentation from drifting. It also makes limits visible: the catalog can describe a native button or a composable card without pretending it has a package install flow, a remote API, or abilities that are not implemented.
Add components with evidence
To add a component, first implement and verify its real UI contract in components/ui. Then add one manifest, adapt it into the Registry, provide a preview that uses the real component, and extend the relevant contract tests.
Do not add a catalog entry only because a component is planned. Keeping unimplemented capabilities out of the registry avoids duplicate implementations, stale documentation, and misleading product claims.