> ## Documentation Index
> Fetch the complete documentation index at: https://docs.mzizi.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Contributing a component

> How to author a new component into the Mzizi registry — the mandatory patterns, the manifest entry, and the checks it has to pass.

Every registry component follows the same shape. This page is what it takes to add one to
[`mzizi-dev/mzizi-registry`](https://github.com/mzizi-dev/mzizi-registry).

<Note>
  **New work goes to Rust.** Mzizi's own components are being rebuilt in Rust as
  [Mzizi Roots](/roots/overview), and that is where new component work goes. The `.tsx`
  steps below are the React build, which keeps working but is deprioritised. A component
  with a Rust sibling lives beside its `.tsx` as a `.rs` file and has a contract test
  asserting the two agree. The design for Roots is still being written, so check the
  registry for its current conventions before starting a Rust component.
</Note>

## 1. Create the component file

Components live under `components/registry/n<number>-<name>/`, filed by the node they sit on.
See [placing a component](/architecture/nodes) if you are unsure which that is.

```tsx theme={null}
// components/registry/n2-primitives/my-component.tsx
"use client"

const myComponentVariants = cva(
  "inline-flex items-center justify-center rounded-md transition-colors",
  {
    variants: {
      variant: {
        default: "bg-primary text-primary-foreground",
        outline: "border border-border bg-transparent text-foreground",
      },
      size: {
        default: "h-14 px-4",
        sm: "h-12 px-3 text-sm",
      },
    },
    defaultVariants: {
      variant: "default",
      size: "default",
    },
  }
)

interface MyComponentProps
  extends React.ComponentProps<"div">,
    VariantProps<typeof myComponentVariants> {}

function MyComponent({ className, variant, size, ...props }: MyComponentProps) {
  return (
    <div
      data-slot="my-component"
      className={cn(myComponentVariants({ variant, size, className }))}
      {...props}
    />
  )
}

export { MyComponent, myComponentVariants }
```

## 2. Follow the mandatory patterns

Every component carries:

* **CVA variants** — `class-variance-authority` for every visual variant, never inline
  conditional classes.
* **`cn()` composition** — all `className` props go through it.
* **A `data-slot` attribute** on the root element.
* **Named exports** — the component and its variants. No default export.
* **Radix UI primitives** for focus, keyboard and screen-reader behaviour where the component
  is interactive.
* **TypeScript types** extending the appropriate HTML element props.

## 3. Add it to `registry.json`

```json theme={null}
{
  "name": "my-component",
  "type": "registry:ui",
  "description": "A brief description of what the component does.",
  "dependencies": ["class-variance-authority"],
  "registryDependencies": [],
  "files": [
    {
      "path": "components/registry/n2-primitives/my-component.tsx",
      "type": "registry:ui"
    }
  ]
}
```

| Field | Required | Description |
| - | - | - |
| `name` | Yes | Kebab-case identifier; matches the file name |
| `type` | Yes | `registry:ui`, `registry:hook`, `registry:lib`, `registry:block`, `registry:base` or `registry:theme` |
| `description` | Yes | One line |
| `dependencies` | Yes | npm packages; may be empty |
| `registryDependencies` | Yes | Other registry item names this needs |
| `files` | Yes | The files that make up the item |

An item with more than one source file where the schema expects exactly one is a manifest bug
rather than a bad request, and the API says so — worth knowing when a component 500s and the
source looks fine.

## 4. Run the generators

```bash theme={null}
pnpm registry:paths        # fill file paths from what is on disk
pnpm registry:metadata     # derive title, categories, docs and author from meta
pnpm registry:normalize    # canonical ordering
pnpm build                 # every generator in write mode; commit what it writes
```

`pnpm build` is the registry's whole build. There is no app to build: it runs every generator,
and CI's `Build` job fails if running it changes a committed file.

## 5. Check it resolves

```bash theme={null}
pnpm registry:validate     # the item resolves on disk and its dependencies are addressable
pnpm registry:verify       # registry.json is canonical (the CI gate)
```

There is no local server: the registry removed its app, `/api/*` included, on 2 October 2026.
Once the change is on `main` and the API's pin has moved to it,
`curl https://api.mzizi.dev/v1/ui/my-component` returns the metadata and the inlined source.

## 6. Add tests

```tsx theme={null}
describe("MyComponent", () => {
  it("renders with default variant", () => {
    render(<MyComponent>Content</MyComponent>)
    expect(screen.getByText("Content")).toBeInTheDocument()
  })

  it("applies variant classes", () => {
    render(<MyComponent variant="outline">Content</MyComponent>)
    expect(screen.getByText("Content")).toHaveClass("border")
  })
})
```

## 7. Run everything

```bash theme={null}
pnpm test
pnpm lint
pnpm typecheck
pnpm build
```

`pnpm check` chains every CI gate locally.

## Checklist

* [ ] CVA + Radix + `cn()` throughout
* [ ] CSS custom properties only — no hardcoded colours
* [ ] `data-slot` on the root element
* [ ] Named exports, no default export
* [ ] Entry in `registry.json` with the right type and dependencies
* [ ] `pnpm registry:validate` and `pnpm registry:verify` pass
* [ ] `pnpm build` leaves no diff
* [ ] Tests, lint and types pass
* [ ] APCA 3.0 contrast, 48px minimum touch targets, keyboard navigation


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.