Comparisons

Comark vs Markdoc

Comark and Markdoc both treat Markdown as data with runtime parsing. Comark adds streaming auto-close, a more compact AST, and renderers for Vue, Svelte, Angular, and terminals.

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

ComarkMarkdoc
Component syntax::callout{type="note"}{% callout type="note" %}
Runtime parsing, no build stepYesYes
Serializable ASTYes, ['tag', props, ...children]Yes, renderable tree of node objects
Streaming incomplete inputAuto-close at every frameNot supported
Official renderersVue, React, Svelte, Angular, Nuxt, HTML, ANSIReact, HTML
Schema validation of tagsNo, components validate their own propsYes, first-class schemas per tag
Variables & functions in contentBindings via :prop="expr" and {{ path }}Built-in $variables and functions
PartialsHandled in application codeBuilt-in {% partial %}
Plugin ecosystemComark plugins + markdown-it pluginsCustom nodes/tags configuration
Syntax lineageMarkdown directives proposal, 5 years of MDC in Nuxt ContentStripe'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 %}

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