Comark vs remark and unified

remark and unified transform Markdown through mdast and hast syntax trees with a large plugin ecosystem. Comark parses into a compact document with component syntax, streaming, and multi-framework renderers.

TL;DR: remark is the Markdown part of unified. It parses Markdown into mdast, transforms it with plugins, and converts it to HTML through hast. It's the foundation of MDX and react-markdown. Comark parses into a compact tuple document with component syntax, completes incomplete Markdown for streaming, and renders natively in Vue, React, Svelte, and Angular. Comark doesn't run remark or rehype plugins.

At a glance

Comarkremark and unified
Syntax treeMarkdownDocument with tuple nodesmdast for Markdown, hast for HTML
Spec complianceCommonMark and GFM with documented differencesStrict CommonMark, GFM with remark-gfm
PluginsComark plugins and markdown-it pluginsremark and rehype plugins
Tree transformsvisit() from comark/utils in a post hookunist-util-visit and other unist utilities
Custom components in contentYes, ::alert{type="info"}Through extensions such as MDX or directives
Incomplete MarkdownautoClose completes the syntaxNo handling in the core
RenderersVue, React, Svelte, Angular, Nuxt, HTML, ANSIHTML with rehype-stringify, React with react-markdown
Raw HTML by defaultKept as writtenDropped unless you set allowDangerousHtml
Back to MarkdownrenderMarkdown()remark-stringify

Two syntax trees or one document

unified works in stages. remark-parse creates mdast, remark plugins transform it, and remark-rehype converts it to hast. Then rehype plugins run, and rehype-stringify outputs HTML:

import { unified } from 'unified'
import remarkParse from 'remark-parse'
import remarkGfm from 'remark-gfm'
import remarkRehype from 'remark-rehype'
import rehypeStringify from 'rehype-stringify'

const file = await unified()
  .use(remarkParse)
  .use(remarkGfm)
  .use(remarkRehype)
  .use(rehypeStringify)
  .process('# Hello **World**')

Comark has one document. GFM tables, strikethrough, task lists, frontmatter, components, and raw HTML are parsed by default. Every renderer reads the same nodes:

import { parseMarkdown } from 'comark'

const document = await parseMarkdown('# Hello **World**')
// [['h1', { id: 'hello-world' }, 'Hello ', ['strong', {}, 'World']]]

mdast nodes are objects with type, children, and fields such as depth. Comark nodes are arrays: a tag, an attributes object, then children. Text nodes are plain strings. The document is JSON-safe, so you can cache it or send it to a client. See the document model.

Transform the tree

Both projects let you change the tree after parsing. In remark, a plugin returns a transformer that visits mdast nodes. In Comark, a plugin uses visit() in a post hook.

import { visit } from 'unist-util-visit'

export default function remarkWordCount() {
  return (tree, file) => {
    let count = 0
    visit(tree, 'text', (node) => {
      count += node.value.split(/\s+/).filter(Boolean).length
    })
    file.data.wordCount = count
  }
}

remark plugins don't run in Comark, because the trees have different shapes. To port one, rewrite its transform with visit(), or look for a markdown-it plugin that adds the same syntax. See the AST API and the plugin API.

Components and streaming

In the unified ecosystem, components in Markdown usually mean MDX, which compiles JSX and JavaScript expressions. Comark components are data: ::alert{type="info"} becomes ['alert', { type: 'info' }, ...]. Your app maps the tag to a component, and nothing in the Markdown executes code. See Comark vs MDX.

remark expects complete input. For LLM output, Comark closes unterminated bold, links, tables, and components on every parse, and renders an open code fence as a growing code block. See AI chat streaming.

Security defaults

remark is cautious by default. remark-rehype drops raw HTML unless you set allowDangerousHtml and add rehype-raw. The unified docs recommend rehype-sanitize for untrusted input.

Comark keeps raw HTML, attributes, and component props as written. Markdown link syntax with a javascript:, vbscript:, or data:text/html URL doesn't become a link. For untrusted input, add the security plugin with a blockedTags list.

What remark does well

  • Spec compliance: remark follows CommonMark strictly. Comark changes some output on purpose. See Differences from CommonMark and GFM.
  • Plugin ecosystem: lint rules, tables of contents, math, MDX, and many rehype transforms. Comark has a smaller set of built-in plugins.
  • Tooling beyond rendering: remark is also used to lint and format Markdown, not only to convert it to HTML.
  • Safe defaults: raw HTML is dropped unless you allow it.

If your pipeline depends on remark or rehype plugins, or you need strict spec output, stay with unified.

FAQ

© 2026 Vercel, Inc.