:::contentbit

Getting started

One command to set up Content Blocks — then your coding agent writes validated content.

Content Blocks is a framework-agnostic block layer for Markdown — the contract between whatever writes your content (you, a CMS, a coding agent) and whatever renders it. Authors write Markdown with directive-style blocks; every block has a schema, validation runs before anything renders, and every example on this site is rendered live by the library it documents:

source
:::comparison{left="Basic" right="Pro"}
- Price | Free | $12/mo
- Support | Community | Priority
:::
rendered
BasicPro
PriceFree$12/mo
SupportCommunityPriority

The shortest path from install to publishable content is two steps: run init, then ask your coding agent to write.

1. One command

pnpm dlx contentbit@latest init
npx contentbit@latest init
bunx contentbit@latest init
yarn dlx contentbit@latest init

It detects your framework and package manager, asks two questions (or none with -y), and leaves you with everything wired:

~/my-app
$ npx contentbit@latest init -y
installing with pnpm: @contentbit/core @contentbit/blocks @contentbit/react
created: blocks/registry.ts
created: content/example.md
created: blocks/components.tsx
created: components/content-blocks.tsx
created: app/example/page.tsx
added script: content:check
created: contentbit-guide.md (LLM authoring instructions)
created: AGENTS.md (contentbit block)
installed: .claude/skills/contentbit-author/SKILL.md
installed: .claude/skills/contentbit-audit/SKILL.md
Agent integration installed — try asking your agent:
"write a blog post about X" or "audit my content"

That's packages for your render target, a custom-block registry, a starter document with a rendered /example page, a content:check validate script, LLM authoring instructions, and the coding-agent integration (skip it with --no-agents).

2. Ask your agent

Your coding agent now knows how to write content for this project. Try:

write a blog post about our new dark mode

audit my content

The installed contentbit-author skill runs the loop a careful human would: fetch the live authoring guide, write, validate, fix every diagnostic until the validator exits 0.

claude
>write a short post about our new dark mode
Skill: contentbit-author
$ contentbit instructions --audience llm --registry ./blocks/registry.tsexit 0
Drafting content/dark-mode.md with a callout and a before/after comparison…
$ contentbit validate content/dark-mode.md --registry ./blocks/registry.tsexit 1
content/dark-mode.md:18:1 error CB_ROW_COLUMNS:::comparison rows require 3 columns (label | left | right). Found 2.hint: Format: - label | left | right
The comparison row is missing its second value — fixing and revalidating.
$ contentbit validate content/dark-mode.md --registry ./blocks/registry.tsexit 0
1 file(s): 0 errors, 0 warnings

This is reliable because the skills contain no schemas. They read everything from the CLI at runtime (the authoring guide, the diagnostics, the stats), so when you add a custom block, every agent picks it up on the next run with nothing to regenerate. Works beyond Claude Code too: AGENTS.md carries the same instructions for Cursor, Codex, Copilot, and friends.

The validation contract

What makes the loop converge is the diagnostic format. Invalid content never renders; it produces errors a machine can act on:

content.md:12:3 error CB_ROW_COLUMNS
:::comparison rows require 3 columns (label | left | right). Found 2.
hint: Format: - label | left | right

Each diagnostic carries the exact source position, a stable code, and a fix hint, so your agent (or your CI) can act on it. The same gate runs in the editor loop, in pnpm run content:check, and before every render.

Under the hood

Prefer wiring things up manually? Install the pieces directly:

pnpm add @contentbit/core @contentbit/blocks
pnpm add @contentbit/html   # static HTML strings
pnpm add @contentbit/react  # React components
pnpm add @contentbit/astro  # .astro components

The whole pipeline is three calls — parse to a source-mapped AST, validate against the registry, render through the adapter of your choice:

import { createBlockRegistry, parseDocument, validateDocument } from '@contentbit/core'
import { genericBlocks } from '@contentbit/blocks'
import { renderToHtml } from '@contentbit/html'

const registry = createBlockRegistry().use(genericBlocks())

const result = validateDocument(parseDocument(markdown), registry)
if (!result.ok) {
  console.error(result.diagnostics) // code, severity, message, exact position
}

const html = renderToHtml(result.document)

In React, ContentBlocks ships headless, accessible defaults for every generic block:

import { ContentBlocks } from '@contentbit/react'

export function Article({ document }: { document: DocumentNode }) {
  return <ContentBlocks document={document} renderMarkdown={(md) => <Markdown source={md} />} />
}

One thing not to skip: Content Blocks parses blocks only — prose and block bodies are handed to a renderMarkdown function you provide (react-markdown, marked, markdown-it, remark…). Without it, **bold** renders as literal characters. Step-by-step wiring for each library: Plug in your Markdown library.

And if headless isn't your thing, a polished Tailwind-styled pack installs through the shadcn registry — components land as editable source files you own:

pnpm dlx shadcn@latest add @contentbit/generic-pack

Next steps

On this page