Switch language to العربية

Templates Factory

PreviousNext

How templates are built, registered, previewed and installed: codebase's equivalent of shadcn/ui blocks.

Templates are codebase's equivalent of shadcn/ui blocks: full pages and page sections registered as registry:block items, shown in the /templates gallery, and installed with the shadcn CLI.

pnpm dlx shadcn@latest add @codebase/pricing-01

They are handled exactly the way ui.shadcn.com handles blocks. See Upstream for the pinned version and the few places we diverge on purpose.

What exists

71 templates in 21 categories.

KindTemplates
App shellsdashboard-01, sidebar-01 to 16, leads-01
Auth and billinglogin-01, login-03, login-04, signup-01 to 05, subscription-01 to 03
Page chromeheader-01 to 05, footer-01 to 05
Marketing sectionshero-01 to 08, features-01 to 04, cta-01 to 03, stats-01 to 02, logos-01 to 02, pricing-01 to 02, testimonials-01 to 03, faq-01 to 02, how-it-works-01 to 02, team-01, contact-01, newsletter-01, blog-01

The registry templates header-01 and footer-01 are not the site's own header and footer. Those live in src/components/template/ and are never overwritten.

Anatomy

src/registry/bases/base/blocks/
├── _registry.ts                  the manifest (one entry per template)
└── faq-01/
    ├── page.tsx                  demo page          registry:page   → app/faq-01/page.tsx
    ├── components/
    │   └── faq-01.tsx            the section        registry:component
    └── faq-01-dictionary.ts      en + ar content    registry:file   → app/faq-01/faq-01-dictionary.ts

Every file name is unique across templates. The shadcn CLI connects a template's files by file name when it installs them, so two templates that both shipped dictionary.ts would get wired to each other. Name parts <name>.tsx, <name>-<part>.tsx and <name>-dictionary.ts.

Imports inside a template:

ImportUse for
@/registry/bases/base/blocks/<name>/...the template's own files
@/components/ui/<primitive>shadcn primitives (Base UI)
@/components/atom/<atom>codebase atoms
@/lib/utilscn

The CLI rewrites these to the consumer's aliases on install.

The section contract

Marketing sections (header, hero, features, CTA, stats, logos, pricing, testimonials, FAQ, how-it-works, team, contact, newsletter, blog, footer) follow one contract.

The component exports its dictionary type and takes dictionary and lang:

components/faq-01.tsx
export type FaqDictionary = {
  eyebrow: string
  title: string
  subtitle: string
  items: { question: string; answer: string }[]
}
 
export function Faq({
  dictionary,
  lang,
  className,
}: {
  dictionary: FaqDictionary
  lang: "en" | "ar"
  className?: string
}) {
  const dir = lang === "ar" ? "rtl" : "ltr"
  return <section dir={dir} className={className}>{/* … */}</section>
}

The dictionary file holds the demo content in English and Arabic:

faq-01-dictionary.ts
import { type FaqDictionary } from "@/registry/bases/base/blocks/faq-01/components/faq-01"
 
export const dictionary: Record<"en" | "ar", FaqDictionary> = {
  en: { eyebrow: "FAQ", title: "Questions, answered" /* … */ },
  ar: { eyebrow: "الأسئلة الشائعة", title: "إجابات لأسئلتك" /* … */ },
}

The demo page reads the locale from the route, so the same template renders English on /en and Arabic, right-to-left, on /ar:

page.tsx
import { Faq } from "@/registry/bases/base/blocks/faq-01/components/faq-01"
import { dictionary } from "@/registry/bases/base/blocks/faq-01/faq-01-dictionary"
 
export default async function Page({
  params,
}: {
  params?: Promise<{ lang?: string }>
}) {
  const { lang } = (await params) ?? {}
  const locale = lang === "ar" ? "ar" : "en"
  return <Faq dictionary={dictionary[locale]} lang={locale} />
}

Rules every section follows:

  • All copy and media come from the dictionary. No hardcoded strings or brand names; demo content is generic ("Acme").
  • RTL first. Logical classes only (ms-, pe-, start-, text-start); directional icons get rtl:rotate-180.
  • Semantic tokens (bg-background, text-muted-foreground, bg-primary) so light and dark both work.
  • Base UI call sites. render instead of asChild, Accordion without type, Select with items.
  • No app coupling. No auth, billing, env vars, stores, database calls or analytics. Links are plain hrefs from the dictionary.
  • Animation with motion, and nothing that ignores prefers-reduced-motion.

Add a template

1. Create the files

Pick the next number in the category (hero-09), and create page.tsx, components/hero-09.tsx and, for a section, hero-09-dictionary.ts.

2. Register it

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

{
  name: "faq-01",
  title: "FAQ 01",
  description:
    "Centered FAQ with rounded, tinted accordion cards that turn solid when open, plus a contact line.",
  type: "registry:block",
  registryDependencies: ["accordion"],
  files: [
    { path: "blocks/faq-01/page.tsx", type: "registry:page", target: "app/faq-01/page.tsx" },
    { path: "blocks/faq-01/components/faq-01.tsx", type: "registry:component" },
    {
      path: "blocks/faq-01/faq-01-dictionary.ts",
      type: "registry:file",
      target: "app/faq-01/faq-01-dictionary.ts",
    },
  ],
  categories: ["faq"],
  meta: { iframeHeight: "800px" },
}
FieldMeaning
descriptionOne sentence describing the look; shown in the viewer toolbar
dependenciesnpm packages beyond React, Next and lucide (for example motion)
registryDependenciesshadcn primitives by bare name; codebase atoms as @codebase/<atom>
categoriesslugs from src/lib/categories.ts
meta.iframeHeightpreview height; tall enough to show the section without scrolling

A new category goes into src/lib/categories.ts. The gallery nav only shows categories that have at least one template.

3. Build the registry

pnpm registry:build

This writes the runtime index (src/registry/__index__.tsx), the lazy component shards, __blocks__.json, the root registry.json, and runs shadcn build to publish public/r/<name>.json.

4. Capture the screenshots

With the dev server on port 3000:

pnpm registry:capture faq-01

Light and dark PNGs land in public/r/styles/base-vega/. The gallery uses them on mobile.

5. Verify

  • /en/templates/<category> and /ar/templates/<category>: the viewer, code tab and file tree.
  • /en/view/base-vega/<name> and /ar/view/base-vega/<name>: the full page, at phone and desktop widths, light and dark.
  • 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 and next build.
PieceFile
Featured listsrc/app/[lang]/(root)/templates/page.tsx
Category pagessrc/app/[lang]/(root)/templates/[...categories]/page.tsx
Viewersrc/components/docs/block-display.tsx, block-viewer.tsx
Full-page previewsrc/app/[lang]/(view)/view/[style]/[name]/page.tsx
Lookupssrc/lib/registry.ts, src/lib/blocks.ts

All of these are prerendered. Old /templates/<name> links redirect to the template inside its category.

Troubleshooting

  • The template is missing from the gallery: run pnpm registry:build; check that its category exists in src/lib/categories.ts.
  • The preview is cut off: raise meta.iframeHeight.
  • An install wires the wrong file: two templates share a file name; rename to <name>-<part>.
  • An install fails to typecheck: an npm import is missing from dependencies, or the template imports something outside ui, atoms and @/lib/utils.