contentbit — structured Markdown components without framework lock-in
Built for LLM-generated content<br>Structured Markdown components without framework lock-in<br>Write Markdown with validated, structured blocks. Render it anywhere. Built for content written by humans, CMSes, and LLMs.<br>Get startedOpen the playground<br>pnpmnpmbunyarn<br>$ pnpm dlx contentbit@latest init
or see a complete article rendered by the library
01·The idea<br>Markdown in, components out<br>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<br>:::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.:::<br>rendered<br>65%<br>Hydration
24h<br>Cold ferment
250g<br>Ball weight
450°C<br>Oven temp
Stand mixerBy hand<br>Dough hook, speed 2, eight minutes. Stop when the dough clears the bowl .
Fresh yeastInstantAmount9g3gWhere to buyBakeriesEverywhereFlavorSlightly richerNeutral<br>TipSame source, every target<br>p]:m-0 [&>p+p]:mt-2">This panel is the real React pack. Prose runs through react-markdown, exactly like your app would wire it.
this is a fragment. read a complete post: 4 blocks, 3 render targets, 0 diagnostics
02·The safety net<br>Errors with line numbers, not broken pages<br>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<br>:::comparison{left="Basic"}<br>- Price | Free<br>:::<br>contentbit validate broken.mdexit 1<br>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<br>One definition, every surface
guide.mdstatic .htmlplain .md
No framework lock-in<br>The content is a protocol. Renderers are adapters: React and static HTML today, plain Markdown always.
- Price | FreeCB_ROW_COLUMNSbroken.md:2:1<br>- Price | Free | $12/mo0 errors, 0 warnings
Validation before render<br>Every block has a schema. Bad content fails with file:line:col diagnostics, not broken pages.
## Dough basics
Weigh everything. Volume<br>measures drift by 20%.
:::callout{type="tip"}<br>Cold ferment for flavor.<br>:::<br>Still just Markdown<br>Documents stay readable in any text editor. Strip the renderer and the content still makes sense.
write regular Markdown by default<br>never invent block names<br>fix diagnostics, don’t bypass them<br>↳ generated from the registry
Made for generated content<br>The registry that validates content also writes the authoring instructions for LLMs, so prompts never drift from the rules.
$ shadcn add @contentbit/tabs<br>components/<br>└─ content-blocks/<br>└─ tabs-block.tsx ← yours now<br>shadcn distribution<br>Styled components install as editable source files through a shadcn registry. You own them after install.
const pricingTable = defineBlock({<br>name: 'pricing-table',<br>props: z.object({ currency: z.enum(['usd', 'eur']) }),<br>content: pipeRows({ columns: ['plan', 'price'] }),<br>authoring: { useWhen: [...], example },<br>})Extensible registry<br>A custom block is a name, a zod props schema, a content model, and authoring guidance, in under 20 lines. It validates, renders, and documents itself from that one definition.
04·The generic pack<br>Eight blocks that work in any niche<br>Pick a block. The example is its real authoring guidance from the registry, the same text LLMs get, rendered live by the styled pack.
All blocks →<br>:::callout:::steps:::key-metrics:::quick-ref:::comparison:::pros-cons:::tabsinteractive:::faqinteractive<br>authoring examplefrom the registry<br>:::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<br>TipWorth knowing<br>p]:m-0 [&>p+p]:mt-2">Always weigh flour — volume measures drift by 20%.
Highlighted note, tip, warning, important, or TLDR box.
05·Styled pack<br>Install the components, own the code<br>The React pack ships through a shadcn registry. Components land in your app as editable source files: Tailwind, your tokens, your rules.<br>pnpmnpmbunyarn<br>$ pnpm dlx shadcn@latest add @contentbit/generic-pack
registry: https://contentbit.dev/r/{name}.json