Switch language to العربية

Atoms Factory

PreviousNext

How atoms are built, documented and installed: codebase's equivalent of shadcn/ui components.

Atoms are codebase's equivalent of shadcn/ui components: small, single-purpose compositions of two or more primitives, registered as registry:component items, documented at /atoms, and installed with the shadcn CLI.

pnpm dlx shadcn@latest add @codebase/labeled-input

Atoms install to components/atom/ in your app, which keeps the databayt component hierarchy: ui, then atom, then template, then block. See Upstream for how codebase maps to shadcn/ui.

Anatomy

An atom is three files and a manifest entry:

FileRoleUpstream equivalent
src/components/atom/labeled-input.tsxthe atomregistry/bases/base/ui/<name>.tsx
src/registry/bases/base/components/_registry.tsits manifest entryui/_registry.ts
src/examples/base/labeled-input-demo.tsxthe docs demoexamples/base/<name>-demo.tsx
content/atoms/(root)/labeled-input.mdxthe docs pagecontent/docs/components/base/<name>.mdx

Atom files are flat (atom/<name>.tsx); only ai/ and modal/ use folders. There is no barrel file.

Write the atom

src/components/atom/labeled-input.tsx
"use client"
 
import * as React from "react"
 
import { cn } from "@/lib/utils"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
 
export interface LabeledInputProps
  extends Omit<React.ComponentProps<typeof Input>, "id"> {
  label: string
  containerClassName?: string
}
 
export function LabeledInput({
  label,
  containerClassName,
  className,
  ...props
}: LabeledInputProps) {
  const id = React.useId()
 
  return (
    <div className={cn("grid gap-2", containerClassName)} data-slot="labeled-input">
      <Label htmlFor={id}>{label}</Label>
      <Input id={id} className={className} {...props} />
    </div>
  )
}

Rules:

  • Compose, don't hand-roll. Use the primitives in src/components/ui/: field for form fields, empty for empty states, item, button-group, input-group, spinner, kbd.
  • Self-contained. An atom may import @/components/ui/*, @/lib/utils, @/hooks/use-mobile, other atoms and npm packages. Never site modules such as @/components/local/*, @/components/table/* or @/hooks/use-toast: they don't exist in the app that installs the atom.
  • Base UI. Use the render prop, never asChild, and no radix-ui imports.
  • RTL first. Logical classes (ms-, pe-, start-, text-start); directional icons get rtl:rotate-180.
  • Semantic tokens (bg-card, text-muted-foreground), never raw colors.
  • Typed props with an exported type, plus className merged with cn().

Register it

Add an entry to src/registry/bases/base/components/_registry.ts:

{
  name: "labeled-input",
  title: "Labeled Input",
  description: "Input with attached label, hint and error message",
  type: "registry:component",
  registryDependencies: ["label", "input"],
  files: [
    { path: "components/atom/labeled-input.tsx", type: "registry:component" },
  ],
  categories: ["form"],
}
FieldMeaning
dependenciesevery npm package the atom imports (for example framer-motion, recharts)
registryDependenciesshadcn primitives by bare name; other atoms as @codebase/<atom>
categoriesone or two of display, form, interactive, ui, ai, animation, layout, data, navigation, feedback, utility

The build adds target: "components/atom/<name>.tsx", which is why atoms install into components/atom/.

Write the demo

Every atom page previews a demo file, the same way shadcn previews button-demo:

src/examples/base/labeled-input-demo.tsx
import { LabeledInput } from "@/components/atom/labeled-input"
 
export default function LabeledInputDemo() {
  return (
    <LabeledInput label="Email" type="email" placeholder="Enter your email" />
  )
}

Pass real props. A demo is typechecked like any other file, which is the point: a preview that renders with missing props is a bug.

Document it

Scaffold the page:

pnpm generate:docs

This writes content/atoms/(root)/<name>.mdx for every registered atom that has no page yet, and adds it to meta.json (the sidebar reads that order). It never overwrites a page you have edited. The page follows shadcn's component-doc pattern:

content/atoms/(root)/labeled-input.mdx
---
title: Labeled Input
description: Input with attached label, hint and error message
component: true
---
 
<ComponentPreview name="labeled-input-demo" />
 
## Installation
 
<CodeTabs>
 
<TabsList>
  <TabsTrigger value="cli">CLI</TabsTrigger>
  <TabsTrigger value="manual">Manual</TabsTrigger>
</TabsList>
<TabsContent value="cli">
 
```bash
npx shadcn@latest add @codebase/labeled-input
```
 
</TabsContent>
 
<TabsContent value="manual">
 
<Steps>
 
<Step>Copy and paste the following code into your project.</Step>
 
<ComponentSource name="labeled-input" title="components/atom/labeled-input.tsx" />
 
<Step>Update the import paths to match your project setup.</Step>
 
</Steps>
 
</TabsContent>
 
</CodeTabs>
 
## Usage
 
```tsx
import { LabeledInput } from "@/components/atom/labeled-input"
```
  • ComponentPreview name="x-demo" renders the demo and shows its source.
  • ComponentSource name="x" shows the atom's source from the registry. src="path" reads any file in the repo instead.
  • If the atom needs npm packages, add an "Install the following dependencies" step to the Manual tab.

Build and verify

pnpm registry:build
pnpm dev

registry:build indexes the demo (src/examples/__index__.tsx) and publishes public/r/<name>.json. Then check:

  • /en/atoms/<name> and /ar/atoms/<name>: preview, code, install tabs.
  • A real install: in a scratch app made with npx shadcn@latest init --template next --base base --preset vega --rtl, register @codebase against http://localhost:3000/r/{name}.json, run npx shadcn add @codebase/<name>, then tsc.

Protected atoms

Some atoms are codebase originals kept on purpose; don't "modernize" them: page-header and page-actions (the legacy shadcn page heading), two-buttons, announcement, fonts, icons, site-heading, modal-system, tabs (TabsNav).

Troubleshooting

  • "Component … not found in registry" on the page: the demo file is missing or the index is stale; run pnpm registry:build.
  • The atom isn't in the sidebar: it's not in content/atoms/(root)/meta.json; run pnpm generate:docs.
  • An install fails with a missing module: add the package to dependencies, or remove an import of a site-only module.