{"slug": "structured-markdown-components-without-framework-lock-in", "title": "Structured Markdown components without framework lock-in", "summary": "Contentbit launched a new open-source library that lets developers write structured, validated Markdown components without being locked into a specific JavaScript framework. The tool uses directive blocks inside ordinary Markdown, which are parsed into a source-mapped AST and validated against schemas before rendering in React, static HTML, or plain Markdown. This approach aims to solve content portability and validation issues for human writers, CMSes, and LLMs generating structured content.", "body_md": "# Structured Markdown components without framework lock-in\n\nWrite Markdown with validated, structured blocks. Render it anywhere. Built for content written by humans, CMSes, and LLMs.\n\n`$ pnpm dlx contentbit@latest init`\n\nor see [a complete article](/blog/llm-markdown-that-cannot-break/) rendered by the library\n\n01·The idea\n\n## Markdown in, components out\n\nAuthors 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.\n\n```\n:::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.:::\n```\n\nDough hook, speed 2, eight minutes. Stop when the dough **clears the bowl**.\n\n| Fresh yeast | Instant | |\n|---|---|---|\n| Amount | 9g | 3g |\n| Where to buy | Bakeries | Everywhere |\n| Flavor | Slightly richer | Neutral |\n\n02·The safety net\n\n## Errors with line numbers, not broken pages\n\nValidation 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.\n\n```\n:::comparison{left=\"Basic\"}\n- Price | Free\n:::\nbroken.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.\n```\n\n03·The system\n\n## One definition, every surface\n\n### No framework lock-in\n\nThe content is a protocol. Renderers are adapters: React and static HTML today, plain Markdown always.\n\n```\n- Price | Free\n- Price | Free | $12/mo\n```\n\n### Validation before render\n\nEvery block has a schema. Bad content fails with file:line:col diagnostics, not broken pages.\n\n```\n## Dough basics\n\nWeigh everything. Volume\nmeasures drift by 20%.\n\n:::callout{type=\"tip\"}\nCold ferment for flavor.\n:::\n```\n\n### Still just Markdown\n\nDocuments stay readable in any text editor. Strip the renderer and the content still makes sense.\n\n↳ generated from the registry\n\n### Made for generated content\n\nThe registry that validates content also writes the authoring instructions for LLMs, so prompts never drift from the rules.\n\n```\ncomponents/\n└─ content-blocks/\n   └─ tabs-block.tsx ← yours now\n```\n\n### shadcn distribution\n\nStyled components install as editable source files through a shadcn registry. You own them after install.\n\n``` js\nconst pricingTable = defineBlock({\n  name: 'pricing-table',\n  props: z.object({ currency: z.enum(['usd', 'eur']) }),\n  content: pipeRows({ columns: ['plan', 'price'] }),\n  authoring: { useWhen: [...], example },\n})\n```\n\n### Extensible registry\n\nA 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.\n\n04·The generic pack\n\n## Eight blocks that work in any niche\n\nPick a block. The example is its real authoring guidance from the registry, the same text LLMs get, rendered live by the styled pack.\n\n[All blocks →](/blocks/)\n\n```\n:::callout{type=\"tip\" title=\"Worth knowing\"}Always weigh flour — volume measures drift by 20%.:::\n```\n\nUse when: Practical advice that prevents a common mistake (tip)\n\nHighlighted note, tip, warning, important, or TLDR box.\n\n05·Styled pack\n\n## Install the components, own the code\n\nThe React pack ships through a shadcn registry. Components land in your app as editable source files: Tailwind, your tokens, your rules.\n\n`$ pnpm dlx shadcn@latest add @contentbit/generic-pack`\n\nregistry: https://contentbit.dev/r/{name}.json", "url": "https://wpnews.pro/news/structured-markdown-components-without-framework-lock-in", "canonical_source": "https://contentbit.dev/", "published_at": "2026-06-12 07:20:01+00:00", "updated_at": "2026-06-12 07:49:43.157639+00:00", "lang": "en", "topics": ["ai-tools"], "entities": ["Contentbit"], "alternates": {"html": "https://wpnews.pro/news/structured-markdown-components-without-framework-lock-in", "markdown": "https://wpnews.pro/news/structured-markdown-components-without-framework-lock-in.md", "text": "https://wpnews.pro/news/structured-markdown-components-without-framework-lock-in.txt", "jsonld": "https://wpnews.pro/news/structured-markdown-components-without-framework-lock-in.jsonld"}}