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.
An atom is three files and a manifest entry:
| File | Role | Upstream equivalent |
|---|---|---|
src/components/atom/labeled-input.tsx | the atom | registry/bases/base/ui/<name>.tsx |
src/registry/bases/base/components/_registry.ts | its manifest entry | ui/_registry.ts |
src/examples/base/labeled-input-demo.tsx | the docs demo | examples/base/<name>-demo.tsx |
content/atoms/(root)/labeled-input.mdx | the docs page | content/docs/components/base/<name>.mdx |
Atom files are flat (atom/<name>.tsx); only ai/ and modal/ use folders. There is no
barrel file.
"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:
src/components/ui/: field for form
fields, empty for empty states, item, button-group, input-group, spinner, kbd.@/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.render prop, never asChild, and no radix-ui imports.ms-, pe-, start-, text-start); directional icons get
rtl:rotate-180.bg-card, text-muted-foreground), never raw colors.className merged with cn().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"],
}| Field | Meaning |
|---|---|
dependencies | every npm package the atom imports (for example framer-motion, recharts) |
registryDependencies | shadcn primitives by bare name; other atoms as @codebase/<atom> |
categories | one 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/.
Every atom page previews a demo file, the same way shadcn previews button-demo:
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.
Scaffold the page:
pnpm generate:docsThis 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:
---
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.pnpm registry:build
pnpm devregistry: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.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.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).
pnpm registry:build.content/atoms/(root)/meta.json; run
pnpm generate:docs.dependencies, or remove an
import of a site-only module.