KKiotaPay UI
Browse documentation

Adding a component

Every item follows the same shape. Deviating from it is what produces components that install cleanly here and break in a consumer's project.

1. Write the source under registry/

Everything published lives under registry/, mirroring the tree a consumer ends up with — registry/components/custom/ for components, registry/hooks/ for hooks, registry/lib/ for utilities. Nothing outside registry/ is ever distributed.

Author against the standard aliases, exactly as a consumer would write them:

'use client'

import * as React from 'react'

import { Input } from '@/components/ui/input'
import { cn } from '@/lib/utils'
import { formatMoney } from '@/lib/kiota-format'

export function PayoutSummary({ className }: { className?: string }) {
  return <div className={cn('text-sm', className)}>{formatMoney(125050)}</div>
}

Rules that matter for distribution:

2. Register it in the nearest sub-catalog

Add an entry to the registry.json in that directory — registry/components/custom/registry.json for a component. Paths are relative to that file and cannot use ../.

{
  "name": "payout-summary",
  "type": "registry:component",
  "title": "Payout Summary",
  "description": "One-line summary of a payout's amount and destination.",
  "author": "KiotaPay <engineering@kiotapay.com>",
  "dependencies": ["lucide-react"],
  "registryDependencies": ["card", "{REGISTRY_URL}/r/kiota-theme.json"],
  "files": [
    {
      "path": "payout-summary.tsx",
      "type": "registry:component",
      "target": "@components/custom/payout-summary.tsx"
    }
  ],
  "categories": ["display", "payments"],
  "docs": "Anything a consumer must do after installing."
}

Checklist for the entry:

A missing registryDependencies entry is invisible here — the primitive already exists in this repository, so previews render fine. It only fails in a consumer's project, as an unresolved import. Check the list against the file's imports rather than against whether the preview works.

3. Add a new sub-catalog if you need a new directory

Creating a directory the root catalog does not know about means adding it to registry.json:

{
  "include": [
    "registry/theme/registry.json",
    "registry/lib/registry.json",
    "registry/hooks/registry.json",
    "registry/components/custom/registry.json",
    "registry/components/charts/registry.json"
  ]
}

Split by directory rather than by theme. The no-../ rule means a catalog can only describe files beneath itself, so directory boundaries are the only ones that actually work.

If the new directory is one consumers will receive files into, mirror their path under registry/ — registry/components/charts/ targeting @components/charts/. Add a matching paths entry in tsconfig.json so it resolves locally with no further configuration.

4. Write the example, then the page

Examples live in components/examples/ as default exports, one per file. The generator turns the filename into the identifier, so payout-summary-demo.tsx becomes the example named payout-summary-demo.

import { PayoutSummary } from '@/components/custom/payout-summary'

export default function PayoutSummaryDemo() {
  return <PayoutSummary />
}

Regenerate the map, then reference the example by name in MDX:

npm run examples:build
<ComponentPreview name="payout-summary-demo" />

The Code tab renders that file's real source. It cannot drift from the preview above it, which is the reason examples are separate files rather than snippets pasted into the page.

npm run dev and npm run build both regenerate the map first, so you only need examples:build by hand when adding a file while the dev server is already running.

Then add app/(docs)/components/<name>/page.mdx following the existing pages — description, <InstallCommand />, usage, props table, examples — and a sidebar entry in lib/nav.ts.

(docs) is a Next.js route group: the parentheses keep the directory out of the URL, so the page above is served at /components/<name>. Every MDX page lives inside it, which leaves the app/ root free for anything that is not documentation — a route handler, an OG-image endpoint, a playground.

5. Verify before publishing

npm run typecheck
npm run registry:build

The build fails loudly on a missing file or a malformed entry, but it cannot tell you that a registryDependencies list is incomplete. The only real check is installing into a scratch project:

npx shadcn@latest add http://localhost:3005/r/payout-summary.json