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.
71 templates in 21 categories.
| Kind | Templates |
|---|---|
| App shells | dashboard-01, sidebar-01 to 16, leads-01 |
| Auth and billing | login-01, login-03, login-04, signup-01 to 05, subscription-01 to 03 |
| Page chrome | header-01 to 05, footer-01 to 05 |
| Marketing sections | hero-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.
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.tsEvery 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:
| Import | Use for |
|---|---|
@/registry/bases/base/blocks/<name>/... | the template's own files |
@/components/ui/<primitive> | shadcn primitives (Base UI) |
@/components/atom/<atom> | codebase atoms |
@/lib/utils | cn |
The CLI rewrites these to the consumer's aliases on install.
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:
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:
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:
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:
ms-, pe-, start-, text-start); directional icons
get rtl:rotate-180.bg-background, text-muted-foreground, bg-primary) so light and dark
both work.render instead of asChild, Accordion without type, Select with
items.hrefs from the dictionary.motion, and nothing that ignores prefers-reduced-motion.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.
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" },
}| Field | Meaning |
|---|---|
description | One sentence describing the look; shown in the viewer toolbar |
dependencies | npm packages beyond React, Next and lucide (for example motion) |
registryDependencies | shadcn primitives by bare name; codebase atoms as @codebase/<atom> |
categories | slugs from src/lib/categories.ts |
meta.iframeHeight | preview 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.
pnpm registry:buildThis 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.
With the dev server on port 3000:
pnpm registry:capture faq-01Light and dark PNGs land in public/r/styles/base-vega/. The gallery uses them on mobile.
/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.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.| Piece | File |
|---|---|
| Featured list | src/app/[lang]/(root)/templates/page.tsx |
| Category pages | src/app/[lang]/(root)/templates/[...categories]/page.tsx |
| Viewer | src/components/docs/block-display.tsx, block-viewer.tsx |
| Full-page preview | src/app/[lang]/(view)/view/[style]/[name]/page.tsx |
| Lookups | src/lib/registry.ts, src/lib/blocks.ts |
All of these are prerendered. Old /templates/<name> links redirect to the template inside
its category.
pnpm registry:build; check that its
category exists in src/lib/categories.ts.meta.iframeHeight.<name>-<part>.dependencies, or the
template imports something outside ui, atoms and @/lib/utils.