Live
Black Hat USAAI BusinessBlack Hat AsiaAI BusinessHow to Build Production-Ready Agentic Systems with Z.AI GLM-5 Using Thinking Mode, Tool Calling, Streaming, and Multi-Turn WorkflowsMarkTechPostComparing Today's Multi-Model DatabasesDEV CommunityBuilding a WeChat Mini Program Pre-Sale System from Scratch: A Builder's LogDEV Community26 Quizzes: What We've Learned About Which Results People Actually ShareDEV CommunityLayered Agentic Retrieval for Retail Floor Questions: A Solo PoCDEV CommunityHow to Handle Sensitive Data Securely in TerraformDEV CommunitySecure Cross-Platform File Sharing: A Unified Solution for Diverse Devices and NetworksDEV CommunityHere's what 'cracking' bitcoin in 9 minutes by quantum computers actually meansCoinDesk AII Tested a Real AI Agent for Security. The LLM Knew It Was Dangerous — But the Tool Layer Executed Anyway.DEV Community“Following the incentives”lesswrong.comI Got Tired of Surprise OpenAI Bills, So I Built a Dashboard to Track ThemDEV CommunitySynthetic Population Testing for Recommendation SystemsDEV CommunityBlack Hat USAAI BusinessBlack Hat AsiaAI BusinessHow to Build Production-Ready Agentic Systems with Z.AI GLM-5 Using Thinking Mode, Tool Calling, Streaming, and Multi-Turn WorkflowsMarkTechPostComparing Today's Multi-Model DatabasesDEV CommunityBuilding a WeChat Mini Program Pre-Sale System from Scratch: A Builder's LogDEV Community26 Quizzes: What We've Learned About Which Results People Actually ShareDEV CommunityLayered Agentic Retrieval for Retail Floor Questions: A Solo PoCDEV CommunityHow to Handle Sensitive Data Securely in TerraformDEV CommunitySecure Cross-Platform File Sharing: A Unified Solution for Diverse Devices and NetworksDEV CommunityHere's what 'cracking' bitcoin in 9 minutes by quantum computers actually meansCoinDesk AII Tested a Real AI Agent for Security. The LLM Knew It Was Dangerous — But the Tool Layer Executed Anyway.DEV Community“Following the incentives”lesswrong.comI Got Tired of Surprise OpenAI Bills, So I Built a Dashboard to Track ThemDEV CommunitySynthetic Population Testing for Recommendation SystemsDEV Community
AI NEWS HUBbyEIGENVECTOREigenvector

Why Markdoc for LLM Streaming UI

Dev.to AIby AbhayApril 3, 20264 min read2 views
Source Quiz

Every AI chatbot I've built hits the same wall. The LLM writes beautiful markdown — headings, bold, lists, code blocks. Then someone asks for a chart. Or a form. Or a data table with sortable columns. Suddenly you need a component rendering layer. And every approach has tradeoffs. That's why I built mdocUI: a streaming-first generative UI library that lets LLMs mix markdown and interactive components in one output stream. The Problem JSON blocks in markdown Some teams embed JSON in fenced code blocks: Here's your revenue data: ```json:chart {"type": "bar", "labels": ["Q1", "Q2", "Q3"], "values": [120, 150, 180]} ``` This works until you're streaming. A JSON object that arrives token-by-token is invalid JSON until the closing brace lands. You either buffer the entire block (killing the stre

Every AI chatbot I've built hits the same wall.

The LLM writes beautiful markdown — headings, bold, lists, code blocks. Then someone asks for a chart. Or a form. Or a data table with sortable columns.

Suddenly you need a component rendering layer. And every approach has tradeoffs.

That's why I built mdocUI: a streaming-first generative UI library that lets LLMs mix markdown and interactive components in one output stream.

The Problem

JSON blocks in markdown

Some teams embed JSON in fenced code blocks:

Here's your revenue data:

Enter fullscreen mode

Exit fullscreen mode

json:chart
{"type": "bar", "labels": ["Q1", "Q2", "Q3"], "values": [120, 150, 180]}

Enter fullscreen mode

Exit fullscreen mode

This works until you're streaming. A JSON object that arrives token-by-token is invalid JSON until the closing brace lands. You either buffer the entire block (killing the streaming experience) or parse incomplete JSON (fragile).

JSX in markdown

Here's your data:

`

Enter fullscreen mode

Exit fullscreen mode

Models get confused. They mix HTML attributes with JSX props. They forget to close tags. The < character appears everywhere in normal text, making streaming parsing ambiguous.

Custom DSLs

Some teams invent their own syntax — [[chart:bar:Q1=120,Q2=150]] or similar. Now you're training the model on a format it's never seen, burning tokens on format instructions, and maintaining a custom parser.

Why Markdoc Tag Syntax

Markdoc is a documentation framework created by Stripe. It extends markdown with custom tag delimiters. In this article, I’ll show them as [% %] so Dev.to doesn’t try to parse them as Liquid:

Here's your revenue data:

[% chart type="bar" labels=["Q1","Q2","Q3"] values=[120,150,180] /%]

Revenue grew 12% quarter-over-quarter.

[% button action="continue" label="Show by region" /%]`

Enter fullscreen mode

Exit fullscreen mode

Three properties make this tag syntax ideal for LLM streaming:

  • Unambiguous delimiter — the opening sequence is something you would never expect in normal prose, standard markdown, or fenced code blocks. A streaming parser can detect it without lookahead or backtracking.

  • Models already know it — Markdoc is in training data (Stripe docs, Cloudflare docs). Models write it correctly without extensive format instructions.

  • Prose and components coexist — no mode switching. The LLM writes markdown and drops components wherever they fit. The parser separates them as tokens arrive.

How mdocUI Works

mdocUI borrows only the tag syntax from Markdoc. We built our own streaming parser from scratch.

Architecture:

LLM tokens → Tokenizer → StreamingParser → ComponentRegistry → Renderer

Enter fullscreen mode

Exit fullscreen mode

The tokenizer is a character-by-character state machine with three states: IN_PROSE, IN_TAG, IN_STRING. As tokens arrive, it separates prose from component tags.

The ComponentRegistry validates tag names and props against Zod schemas. Invalid tags get error boundaries, not crashes.

The Renderer maps AST nodes to React components. Every component is theme-neutral — currentColor, inherit, no hardcoded colors. Swap any component with your own.

Getting Started

pnpm add @mdocui/core @mdocui/react

Enter fullscreen mode

Exit fullscreen mode

import { generatePrompt } from '@mdocui/core' import { createDefaultRegistry, defaultGroups } from '@mdocui/react' import { Renderer, useRenderer, defaultComponents } from '@mdocui/react'

const registry = createDefaultRegistry()

// Auto-generate system prompt from your component registry const systemPrompt = generatePrompt(registry, { preamble: 'You are a helpful assistant.', groups: defaultGroups, })

// In your React component function Chat() { const { nodes, isStreaming, push, done } = useRenderer({ registry })

return ( console.log(event)} /> ) }`

Enter fullscreen mode

Exit fullscreen mode

24 components are included: chart, table, stat, card, grid, tabs, form, button, callout, accordion, progress, badge, image, code-block, and more.

What's Next

mdocUI is alpha (0.6.x). The API is stabilizing. We're working on:

  • Vue, Svelte, and Solid renderers

  • Vercel AI SDK useChat bridge

  • Browser devtools for AST inspection

  • VS Code extension for Markdoc-style tag syntax highlighting

Try the playground: https://mdocui.vercel.app GitHub: https://github.com/mdocui/mdocui Docs: https://mdocui.github.io

Feedback, issues, and PRs welcome.

Was this article helpful?

Sign in to highlight and annotate this article

AI
Ask AI about this article
Powered by Eigenvector · full article context loaded
Ready

Conversation starters

Ask anything about this article…

Daily AI Digest

Get the top 5 AI stories delivered to your inbox every morning.

Knowledge Map

Knowledge Map
TopicsEntitiesSource
Why Markdoc…modeltrainingassistantrevenuegenerative …componentDev.to AI

Connected Articles — Knowledge Graph

This article is connected to other articles through shared AI topics and tags.

Knowledge Graph100 articles · 170 connections
Scroll to zoom · drag to pan · click to open

Discussion

Sign in to join the discussion

No comments yet — be the first to share your thoughts!