Comark vs Markdoc
TL;DR: Markdoc (built by Stripe) and Comark share a philosophy: Markdown is data, components are declared in plain text, and parsing happens at runtime without a build step. They differ in what's built on top of the parser. Comark adds streaming auto-close for AI output, a more compact array-based AST, official renderers for Vue, Svelte, Angular, HTML, and ANSI, and compatibility with the markdown-it plugin ecosystem.
At a Glance
| Comark | Markdoc | |
|---|---|---|
| Component syntax | ::callout{type="note"} | {% callout type="note" %} |
| Runtime parsing, no build step | Yes | Yes |
| Serializable AST | Yes, ['tag', props, ...children] | Yes, renderable tree of node objects |
| Streaming incomplete input | Auto-close at every frame | Not supported |
| Official renderers | Vue, React, Svelte, Angular, Nuxt, HTML, ANSI | React, HTML |
| Schema validation of tags | No, components validate their own props | Yes, first-class schemas per tag |
| Variables & functions in content | Bindings via :prop="expr" and {{ path }} | Built-in $variables and functions |
| Partials | Handled in application code | Built-in {% partial %} |
| Plugin ecosystem | Comark plugins + markdown-it plugins | Custom nodes/tags configuration |
| Syntax lineage | Markdown directives proposal, 5 years of MDC in Nuxt Content | Stripe's internal docs platform |
Shared Philosophy
Both tools reject the MDX model of compiling content into code. A Markdoc tag and a Comark component are declarations, not expressions:
{% callout type="note" %}
Useful information with **Markdown** inside.
{% /callout %}::callout{type="note"}
Useful information with **Markdown** inside.
::Both parse to a serializable tree, both resolve components at render time from a registry, and both can run in a request handler. If you agree with "Markdown as data, not code", both are defensible choices. The differences are in the details below.
Streaming
Markdoc was built for documentation: complete files, validated against schemas, rendered as pages. Incomplete input is a parse-time problem it doesn't address.
Comark treats partial input as a primary use case. Auto-close completes unterminated syntax, including half-open ::component blocks, so AI-generated content renders correctly token by token:
import { autoCloseMarkdown } from 'comark'
autoCloseMarkdown('::callout\nStreaming in **prog')
// '::callout\nStreaming in **prog**\n::'AST Shape
Both produce trees; the shapes differ in weight. Comark nodes are plain arrays with a fixed layout:
['callout', { type: 'note' }, ['p', {}, 'Useful information']]Markdoc nodes are objects carrying type, tag, attributes, children, and transform metadata. Comark's format is designed to be sent over the wire: parse on the server, JSON.stringify the result, and hydrate any renderer with it. See the AST reference.
Framework Coverage
Markdoc officially renders to React and HTML. Comark ships official renderers for Vue, React, Svelte, Angular, and Nuxt, plus HTML and ANSI string output, all consuming the same AST.
What Markdoc Does Well
- Schema validation: Markdoc lets you declare each tag's allowed attributes and children, then validate documents against those schemas before rendering. Comark has no schema layer; invalid props surface in the component, and structural checks are yours to write with the AST API.
- Built-in variables, functions, and partials: Markdoc resolves
$variables, calls functions, and includes partials inside content. Comark keeps logic in the application: the binding plugin interpolates frontmatter and runtime data, and composition happens in your components.
For large documentation teams that need to enforce content structure editorially, Markdoc's validation layer is a genuine advantage.
FAQ
{% %} syntax is specific to Markdoc.visit(), or validate props inside components. A schema layer like Markdoc's is not currently provided.{{ path }} expressions from frontmatter, meta, or runtime data, and :prop="expression" binds dynamic values to component props.Comark vs Streamdown
Streamdown streams Markdown in React. Comark streams Markdown with custom component syntax in Vue, React, Svelte, and Angular, with a decoupled parser and serializable AST.
Why Comark?
Markdown is human-readable, token-efficient, and AI-native. Comark extends it to interactive components with runtime parsing, multi-framework rendering, and first-class streaming.