:::contentbit

Structured Markdown components without framework lock-in

Write Markdown with validated, structured blocks. Render it anywhere. Built for content written by humans, CMSes, and LLMs.

$ pnpm add @contentbit/core @contentbit/blocks

or see a complete article rendered by the library

01·The idea

Markdown in, components out

Authors write directive blocks inside ordinary Markdown. The parser builds a source-mapped AST, the registry validates it, and your renderer of choice takes it from there. Below: the actual styled pack rendering live.

guide.md
:::key-metrics- 65% | Hydration- 24h | Cold ferment- 250g | Ball weight- 450°C | Oven temp::: :::tabs::tab{title="Stand mixer"}Dough hook, speed 2, eight minutes. Stop when the dough **clears the bowl**.::tab{title="By hand"}Fold every 30 minutes, four times. Slower, same gluten.::: :::comparison{left="Fresh yeast" right="Instant"}- Amount | 9g | 3g- Where to buy | Bakeries | Everywhere- Flavor | Slightly richer | Neutral::: :::callout{type="tip" title="Same source, every target"}This panel is the real React pack — prose runs through [react-markdown](https://github.com/remarkjs/react-markdown), exactly like your app would wire it.:::
rendered
65%
Hydration
24h
Cold ferment
250g
Ball weight
450°C
Oven temp

Dough hook, speed 2, eight minutes. Stop when the dough clears the bowl.

Fresh yeastInstant
Amount9g3g
Where to buyBakeriesEverywhere
FlavorSlightly richerNeutral

02·The safety net

Errors with line numbers, not broken pages

Validation runs before rendering — in your editor, your CI, or your agent loop. Diagnostics carry a code, a position, and a fix hint, so an LLM can repair its own output.

broken.md
:::comparison{left="Basic"}
- Price | Free
:::
contentbit validate broken.mdexit 1
broken.md:1:1 error CB_PROPS_INVALIDcomparison: prop "right" Invalid input: expected string, received undefinedbroken.md:2:1 error CB_ROW_COLUMNS:::comparison rows require 3 columns (label | left | right). Found 2.hint: Format: - label | left | rightbroken.md:1:1 error CB_ROW_COUNT:::comparison needs at least 2 rows, found 0.

03·The system

One definition, every surface

guide.md
<React />static .htmlplain .md

No framework lock-in

The content is a protocol. Renderers are adapters: React and static HTML today, plain Markdown always.

- Price | Free
CB_ROW_COLUMNSbroken.md:2:1
- Price | Free | $12/mo
0 errors, 0 warnings

Validation before render

Every block has a schema. Bad content fails with file:line:col diagnostics — not broken pages.

## Dough basics

Weigh everything. Volume
measures drift by 20%.

:::callout{type="tip"}
Cold ferment for flavor.
:::

Still just Markdown

Documents stay readable in any text editor. Strip the renderer and the content still makes sense.

write regular Markdown by default
never invent block names
fix diagnostics, don’t bypass them

↳ generated from the registry

Made for generated content

The registry that validates content also writes the authoring instructions for LLMs, so prompts never drift from the rules.

$ shadcn add @contentbit/tabs
components/
└─ content-blocks/
   └─ tabs-block.tsx ← yours now

shadcn distribution

Styled components install as editable source files through a shadcn registry. You own them after install.

const pricingTable = defineBlock({
  name: 'pricing-table',
  props: z.object({ currency: z.enum(['usd', 'eur']) }),
  content: pipeRows({ columns: ['plan', 'price'] }),
  authoring: { useWhen: [...], example },
})

Extensible registry

A custom block is a name, a zod props schema, a content model, and authoring guidance — under 20 lines. It validates, renders, and documents itself from that one definition.

04·The generic pack

Eight blocks that work in any niche

Pick a block. The example is its real authoring guidance from the registry — the same text LLMs get — rendered live by the styled pack.

authoring example
:::callout{type="tip" title="Worth knowing"}Always weigh flour — volume measures drift by 20%.:::

Use when: Practical advice that prevents a common mistake (tip)

rendered

Highlighted note, tip, warning, important, or TLDR box.

05·Styled pack

Install the components, own the code

The React pack ships through a shadcn registry. Components land in your app as editable source files — Tailwind, your tokens, your rules.

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

registry: https://contentbit.dev/r/{name}.json