Add a new reusable component to the shadcn registry with fixtures, demo, and full registration
You are adding a new reusable component to the @worthingtravis/ui-registry shadcn registry. Follow every step exactly — skipping any step will break consumer installs or the preview site.
$ARGUMENTS is the kebab-case component name (e.g., accordion, code-block, avatar-stack).
If no name is provided, ask the user what component they want to add.
registry/new-york/ <- Installable components (shadcn registry)
{name}/
{name}.tsx <- Pure presentation, exported {Name}Props interface
components/ <- Sub-components (optional, for complex items)
hooks/ <- Hooks scoped to this item (optional)
lib/ <- Utilities scoped to this item (optional)
fixtures/
{name}.fixtures.ts <- Typed fixture data (ALL_FIXTURES export)
demos/
{name}.demo.tsx <- PreviewLabConfig wiring fixtures → component
lib/
types.ts <- Shared types (RegistryEntry, RegistryCategory, Variant, PreviewLabConfig)
registry.ts <- Central REGISTRY array, CATEGORY_ORDER, groupedRegistry()
utils.ts <- cn() and shared helpers
components/ <- App-only UI (preview-lab, site-header, sidebar, etc.)
Components are grouped in the sidebar under these ordered categories (defined in lib/registry.ts):
Every RegistryEntry requires a category field matching one of these.
Violations break shadcn build or consumer installs.
registry/new-york/{name}/{name}.tsxcomponents/, hooks/, lib/ directories within the item foldernew-york — always use this@/registry/new-york/... paths@/lib/utils, @/components/ui/button) are fine./foo) between registry itemsname, type, title, description, filespath (relative from project root), typeregistry:component, registry:block, registry:hook, registry:libregistryDependencies: array of other registry item names this depends ondependencies: array of npm package names (e.g., ["lucide-react"])css: keyframe/utility CSS the item requires{Name}Props interface with JSDoc on each propany, no Record<string, unknown> — concrete types everywhereBASE + fx() override patternIf copying or adapting a component from another project, review the source for anything that must NOT be carried over:
text-twitch → use text-primary insteadReplace all of the above with:
@/lib/utils or lucide-reactrenderLink prop pattern for framework-specific routing (Next.js Link, etc.)If unsure whether something is sensitive, strip it and make it a prop.
File: registry/new-york/$ARGUMENTS/$ARGUMENTS.tsx
Rules:
"use client" directive if the component uses hooks, state, or browser APIs{PascalName}Props interface with JSDoc on every propany types — use concrete types and discriminated unionscn() from @/lib/utils for className merging--color-primary (not --accent) for theme colors in inline stylestext-primary, border-primary, bg-primary for Tailwind classes@/registry/new-york/{name}/{name} (never relative imports)renderLink prop so consumers can provide their own <Link> component. Default fallback should use <div> or <span> (not <a>) to avoid nested anchor issues when the component is rendered inside a link wrapper (e.g., ComponentCell on the home page).File: fixtures/$ARGUMENTS.fixtures.ts
import type { FooProps } from "@/registry/new-york/foo/foo";
export type FooFixture = Omit<FooProps, "className" | "onEvent">;
type Fixture = FooFixture;
const BASE: Fixture = { /* sensible defaults */ };
const fx = (overrides: Partial<Fixture> = {}): Fixture => ({ ...BASE, ...overrides });
export const ALL_FIXTURES: Record<string, Fixture> = {
"Default": BASE,
"Empty": fx({ items: [] }),
"Full": fx({ items: MANY_ITEMS }),
};
Rules:
@/registry/new-york/{name}/{name}ALL_FIXTURES as Record<string, Fixture> with at least 3 named fixturesFile: demos/$ARGUMENTS.demo.tsx
"use client";
import { Foo } from "@/registry/new-york/foo/foo";
import { ALL_FIXTURES, type FooFixture } from "@/fixtures/foo.fixtures";
import type { PreviewLabConfig, PropMeta } from "@/lib/types";
const propsMeta: PropMeta[] = [
{ name: "bar", type: "string", required: true, description: "..." },
];
export const config: PreviewLabConfig<FooFixture> = {
title: "Foo",
description: "...",
tags: ["..."],
usageCode: USAGE,
fixtures: ALL_FIXTURES,
render: (fixture) => <Foo {...fixture} />,
propsMeta,
};
Rules:
usageCode with realistic import + JSX examplespropsMeta matching every prop in the Props interfaceAdd an entry to the REGISTRY array in the correct category section:
{
name: "$ARGUMENTS",
description: "One-line description",
lab: () => import("@/demos/$ARGUMENTS.demo"),
tags: ["relevant", "tags"],
category: "Terminal", // or "Profile & Avatar", "Progression", "Animation", "Navigation", "Interactive"
},
Add an entry to the items array:
{
"name": "$ARGUMENTS",
"type": "registry:component",
"title": "Pascal Name",
"description": "One-line description",
"author": "worthingtravis",
"registryDependencies": [],
"dependencies": [],
"files": [
{
"path": "registry/new-york/$ARGUMENTS/$ARGUMENTS.tsx",
"type": "registry:component"
}
],
"categories": ["relevant-category"]
}
Both must pass with zero errors:
npx tsc --noEmit
pnpm run build:registry
If either fails, fix before proceeding.
registry/new-york/$ARGUMENTS/$ARGUMENTS.tsx with exported Props interfacefixtures/$ARGUMENTS.fixtures.ts with BASE + fx() patterndemos/$ARGUMENTS.demo.tsx with PreviewLabConfig (interactive!)lib/registry.ts REGISTRY array with correct categoryregistry.json with all dependencies listednpx tsc --noEmit passespnpm run build:registry passesany types@/registry/new-york/... paths<div>/<span> (not <a>) to avoid nested anchor errorsprimary (not accent or app-specific tokens)