---
title: "Comark vs markdown-to-jsx"
description: "markdown-to-jsx renders JSX tags in Markdown with zero dependencies and many output targets. Comark adds a component syntax with YAML props and named slots, streaming auto-close, and Svelte, Angular, and terminal renderers."
canonical_url: "https://comark.dev/compare/comark-vs-markdown-to-jsx"
---
# Comark vs markdown-to-jsx

> markdown-to-jsx renders JSX tags in Markdown with zero dependencies and many output targets. Comark adds a component syntax with YAML props and named slots, streaming auto-close, and Svelte, Angular, and terminal renderers.

**TL;DR**: [markdown-to-jsx](https://github.com/quantizor/markdown-to-jsx) is a zero-dependency Markdown compiler. It renders JSX tags in the content as your own components, and it outputs React, React Native, Solid, Vue, HTML, and Markdown. Comark uses a component syntax that isn't JSX and completes unterminated syntax while a response streams. It renders to Vue, React, Svelte, Angular, HTML, and the terminal.

## At a glance

|                           | Comark                                                                                                                            | markdown-to-jsx                                                    |
| ------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ |
| Components in the content | `::alert{type="info"}` blocks and `:badge[New]` inline                                                                            | PascalCase JSX tags, for example `<DatePicker timezone="UTC+5" />` |
| Props                     | Inline attributes, typed `:prop` values, YAML props block                                                                         | JSX props; arrays, objects, and booleans are parsed                |
| Named slots               | Yes, `#footer` maps to a slot or prop                                                                                             | Nested content arrives as `children`                               |
| Incomplete Markdown       | `autoClose` completes it on every parse (on by default)                                                                           | `optimizeForStreaming` hides it until it closes (off by default)   |
| Outputs                   | Vue, React, Svelte, Angular, HTML, ANSI, Markdown                                                                                 | React, React Native, Solid, Vue, HTML, Markdown                    |
| Dependencies              | markdown-exit, js-yaml, htmlparser2, entities                                                                                     | None                                                               |
| Raw HTML default          | Parsed into nodes; add the [security plugin](https://comark.dev/plugins/built-in/security) with `blockedTags` for untrusted input | `tagfilter` escapes dangerous tags; raw HTML is always sanitized   |
| Extensions                | Comark plugins and [markdown-it plugins](https://comark.dev/plugins/custom/markdown-it)                                           | `overrides`, `renderRule`, `createElement`; no plugin system       |
| Parsed output             | Serializable [`MarkdownDocument`](https://comark.dev/getting-started/document-model)                                              | AST from `parser()`, rendered with `astToJSX()` or `astToHTML()`   |

## Two ways to write a component

Both libraries let the content place your components. The difference is the syntax that authors and models write.

markdown-to-jsx reads JSX. You register the component in `overrides`, and the parser converts JSON-like prop values for you:

```tsx [markdown-to-jsx]
import Markdown from 'markdown-to-jsx/react'
import Table from './Table'

const content = `<Table columns={['Name', 'Age']} data={[['Alice', 30], ['Bob', 25]]} />`

export function Page() {
  return <Markdown options={{ overrides: { Table } }}>{content}</Markdown>
}
```

Function props such as `onClick={() => ...}` stay strings. The `evalUnserializableExpressions` option evaluates them, and its documentation marks it as dangerous for user input.

Comark reads its own [component syntax](https://comark.dev/syntax/components). Props go in `{}` or in a YAML block, content goes in slots, and components nest with extra colons:

```mdc [content.md]
::data-table
---
columns:
  - Name
  - Age
rows:
  - [Alice, 30]
  - [Bob, 25]
---
Team members.

#footer
Updated weekly.
::
```

```tsx [Page.tsx]
import { Markdown } from '@comark/react'
import DataTable from './DataTable'

export function Page({ content }: { content: string }) {
  return <Markdown components={{ DataTable }}>{content}</Markdown>
}
```

In React, the default slot arrives as `children` and `#footer` arrives as the `slotFooter` prop. See [named slots](https://comark.dev/rendering/react#named-slots).

JSX is familiar to React developers. Comark syntax is closer to Markdown, so writers and CMS editors can use it without knowing JSX. Neither syntax runs code from the content.

## Streaming

Both libraries have a strategy for half-written Markdown, but the strategies differ.

markdown-to-jsx `optimizeForStreaming` holds back incomplete structures until they close. Unclosed bold, links, inline code, and HTML tags render nothing until the closing delimiter arrives. Fenced code blocks render as they arrive. A table waits for its first data row.

Comark [`autoClose`](https://comark.dev/reference/auto-close) completes the syntax instead. `**bold` renders as bold at once, and an open `::alert` renders as a component while its content streams:

```tsx
import { Markdown } from '@comark/react'

<Markdown streaming={isStreaming} caret>{text}</Markdown>
```

While `streaming` is `true`, the `caret` prop adds a blinking cursor after the last text node. Try it in the [streaming playground](https://comark.dev/stream).

## Security defaults

markdown-to-jsx is stricter out of the box. Its `tagfilter` option is on by default and escapes `script`, `iframe`, `style`, and similar tags. Its raw HTML sanitization is always on. It strips `on*` handlers, `srcdoc`, and `javascript:`, `vbscript:`, and non-image `data:` URLs.

Comark is open by default. Markdown link syntax with an unsafe URL doesn't become a link, and the framework renderers never forward `innerHTML` props. Raw HTML, its attributes, and its `<script>` tags are kept, though. For model output or user content, add the [security plugin](https://comark.dev/plugins/built-in/security) and list the tags to remove in `blockedTags`:

```ts
import security from '@comark/react/plugins/security'

const plugins = [security({ blockedTags: ['script', 'iframe', 'object', 'embed', 'form', 'style'] })]
```

## What markdown-to-jsx does well

- **Zero dependencies**: Comark's parser bundle is larger, because it includes markdown-exit, a YAML parser, an HTML parser, and entity tables.
- **Output targets**: React Native and Solid are outputs that Comark doesn't have.
- **React Server Components**: the `Markdown` component detects RSC and needs no `'use client'` directive. Comark's [`MarkdownDocument`](https://comark.dev/rendering/react#code-markdowndocument) also renders in a Server Component.
- **Secure defaults**: the tag filter and raw HTML sanitization need no setup.

If you render trusted Markdown with JSX components in React, React Native, or Solid, markdown-to-jsx is a good fit. Choose Comark when you need components with named slots, streaming that completes syntax, or Svelte, Angular, or terminal output.

## FAQ

::accordion
  :::accordion-item{label="Can I keep JSX tags in my content with Comark?"}
  Not as JSX. Comark parses `<Table ...>` as an HTML tag: the name becomes `table` and `{...}` values aren't parsed. Rewrite these tags as `::table{prop="value"}` with typed `:prop` values or a YAML props block. See [component syntax](https://comark.dev/syntax/components).
  :::

  :::accordion-item{label="Does Comark support React Native or Solid?"}
  No. Comark renders to Vue, React, Svelte, Angular, HTML, and ANSI. For React Native or Solid, markdown-to-jsx has dedicated entry points.
  :::

  :::accordion-item{label="Can I parse on the server and render on the client?"}
  Yes. `parseMarkdown()` returns a JSON-safe `MarkdownDocument`. Store it, cache it, or send it, then render it with `<MarkdownDocument>`. See the [document model](https://comark.dev/getting-started/document-model).
  :::

  :::accordion-item{label="Can I use markdown-it plugins?"}
  Yes. Comark's parser is markdown-exit, a TypeScript rewrite of markdown-it that keeps its plugin API. See [markdown-it plugins](https://comark.dev/plugins/custom/markdown-it).
  :::
::

---

- [Component syntax](https://comark.dev/syntax/components)
- [Playground](https://comark.dev/play)


## Sitemap

See the full [sitemap](https://comark.dev/sitemap.md) for all pages.
