Rendering

Render Comark in React

Learn how to render Comark in a React application with custom components, plugins, and Next.js support.

The <Comark> component is the simplest way to render markdown in React. Pass markdown content directly and it handles parsing and rendering automatically.

Unlike Vue, the React <Comark> component does not require <Suspense> wrapping. Async plugins are handled internally.

Basic Usage

App.tsx
import { Comark } from '@comark/react'
import Alert from './Alert.tsx'

const content = `# Hello World

This is **markdown** with Comark components.

::alert{type="info"}
This is an alert!
::
`

export default function App() {
  return <Comark components={{ Alert }}>{content}</Comark>
}

Props

PropTypeDefaultDescription
childrenReact.ReactNode-Markdown content to parse and render
markdownstring''Alternative to children - markdown content as prop
optionsParseOptions{}Parser options (autoUnwrap, autoClose, etc.)
pluginsComarkPlugin[][]Array of plugins (highlight, emoji, toc, etc.)
componentsRecord<string, ComponentType>{}Custom React component mappings
componentsManifest(name: string) => Promise<Component>undefinedDynamic component resolver function
streamingbooleanfalseEnable streaming mode
caretboolean | { class: string }falseAppend caret to last text node
classNamestringundefinedCSS class for wrapper element

Using the markdown Prop

Instead of using children, you can pass markdown content via the markdown prop:

App.tsx
import { Comark } from '@comark/react'

const content = '# Hello\n\nThis is **markdown**.'

export default function App() {
  return <Comark markdown={content} />
}

With Parser Options

Use the options prop to configure parser behavior:

App.tsx
import { Comark } from '@comark/react'

const content = '# Hello World'

const options = {
  autoUnwrap: true,  // Remove <p> wrappers from single-paragraph containers
  autoClose: true    // Auto-close incomplete syntax
}

export default function App() {
  return <Comark options={options}>{content}</Comark>
}

With Plugins

Use the plugins prop to add functionality like syntax highlighting, emoji support, or table of contents:

App.tsx
import { Comark } from '@comark/react'
import highlight from '@comark/react/plugins/highlight'
import githubLight from '@shikijs/themes/github-light'
import githubDark from '@shikijs/themes/github-dark'

const content = `
\`\`\`javascript
console.log("Hello, World!")
\`\`\`
`

const plugins = [
  highlight({
    themes: {
      light: githubLight,
      dark: githubDark
    }
  })
]

export default function App() {
  return <Comark plugins={plugins}>{content}</Comark>
}

With Custom Components

App.tsx
import { Comark } from '@comark/react'
import CustomAlert from './components/Alert'
import CustomCard from './components/Card'

const components = {
  alert: CustomAlert,
  card: CustomCard,
}

const content = `
::alert{type="warning"}
Important message here
::

::card{title="My Card"}
Card content
::
`

export default function App() {
  return <Comark components={components}>{content}</Comark>
}

Live Editor Example

Editor.tsx
import { useState } from 'react'
import { Comark } from '@comark/react'

export default function MarkdownEditor() {
  const [content, setContent] = useState('# Edit me!\n\nType **markdown** here.')

  return (
    <div className="editor">
      <textarea
        value={content}
        onChange={(e) => setContent(e.target.value)}
      />
      <div className="preview">
        <Comark>{content}</Comark>
      </div>
    </div>
  )
}

Summary Mode

Render only content before <!-- more -->:

ArticleCard.tsx
import { Comark } from '@comark/react'

const content = `# Article Title

This is the summary shown in listings.

<!-- more -->

This is the full article content.
`

export default function ArticleCard() {
  // Only renders "This is the summary shown in listings."
  return <Comark summary>{content}</Comark>
}

With Tailwind CSS

App.tsx
<Comark
  value={content}
  className="prose prose-slate lg:prose-xl dark:prose-invert"
/>

Define Comark Component

The defineComarkComponent helper creates a pre-configured Comark component with default options, plugins, and components. This is useful for creating custom Comark components that can be reused throughout your application with consistent configuration.

Basic Usage

comark.ts
import { defineComarkComponent } from '@comark/react'

// Create a custom Comark component with default configuration
export const MyComark = defineComarkComponent({
  name: 'MyComark',
  // Parser options
  autoUnwrap: true,
  autoClose: true,
})

With Plugins

comark.ts
import { defineComarkComponent } from '@comark/react'
import math from '@comark/react/plugins/math'
import mermaid from '@comark/react/plugins/mermaid'

export const DocsComark = defineComarkComponent({
  name: 'DocsComark',
  plugins: [
    math(),
    mermaid(),
  ],
})

With Custom Components

comark.ts
import { defineComarkComponent } from '@comark/react'
import { Math } from '@comark/react/plugins/math'
import { Mermaid } from '@comark/react/plugins/mermaid'
import CustomAlert from './components/CustomAlert'
import CustomCard from './components/CustomCard'

export const AppComark = defineComarkComponent({
  name: 'AppComark',
  components: {
    Math,
    Mermaid,
    alert: CustomAlert,
    card: CustomCard,
  },
})

With Syntax Highlighting

comark.ts
import { defineComarkComponent } from '@comark/react'
import highlight from '@comark/react/plugins/highlight'
import githubLight from '@shikijs/themes/github-light'
import githubDark from '@shikijs/themes/github-dark'

export const CodeComark = defineComarkComponent({
  name: 'CodeComark',
  plugins: [
    highlight({
      themes: {
        light: githubLight,
        dark: githubDark,
      },
    }),
  ],
})

Complete Configuration Example

comark.ts
import { defineComarkComponent } from '@comark/react'
import math from '@comark/react/plugins/math'
import mermaid from '@comark/react/plugins/mermaid'
import highlight from '@comark/react/plugins/highlight'
import githubLight from '@shikijs/themes/github-light'
import githubDark from '@shikijs/themes/github-dark'
import { Math } from '@comark/react/plugins/math'
import { Mermaid } from '@comark/react/plugins/mermaid'
import CustomAlert from './components/CustomAlert'

export const AppComark = defineComarkComponent({
  name: 'AppComark',

  // Plugins
  plugins: [
    math(),
    mermaid(),
    highlight({
      themes: {
        light: githubLight,
        dark: githubDark,
      },
    }),
  ],

  // Custom components
  components: {
    Math,
    Mermaid,
    alert: CustomAlert,
  },
})

Using the Defined Component

Once defined, use it like the standard <Comark> component:

App.tsx
import { AppComark } from './comark'

const content = `
# Math Example

Inline math: $E = mc^2$

$$
\\int_0^\\infty e^{-x^2} dx = \\frac{\\sqrt{\\pi}}{2}
$$

::alert{type="info"}
This uses pre-configured components and plugins!
::
`

export default function App() {
  return (
    <>
      {/* All configuration is already included */}
      <AppComark>{content}</AppComark>

      {/* Can still override per-instance */}
      <AppComark components={{ alert: DifferentAlert }}>
        {content}
      </AppComark>
    </>
  )
}

Configuration Options

interface DefineComarkComponentOptions {
  // Component display name (for React DevTools)
  name?: string

  // Plugins
  plugins?: ComarkPlugin[]

  // Custom components
  components?: Record<string, React.ComponentType<any>>
}

Benefits

  1. Centralized Configuration: Define options once, use everywhere
  2. Consistent Behavior: Same plugins and components across your app
  3. Type Safety: Full TypeScript support
  4. Easy Overrides: Instance-level props can override defaults
  5. Better DevTools: Named components appear in React DevTools

Merging Behavior

When using the defined component, configurations are merged:

Page.tsx
import { AppComark } from './comark'
import SpecialAlert from './SpecialAlert'
import emoji from 'comark/plugins/emoji'

export default function Page() {
  return (
    <AppComark
      plugins={[emoji()]}
      components={{ alert: SpecialAlert }}
    >
      {content}
    </AppComark>
  )
}

Merge rules:

  • plugins: Arrays are concatenated (config plugins + prop plugins)
  • components: Props override config (prop components take precedence)
  • Other options: Props override config

Real-World Example

comark.ts
import { defineComarkComponent } from '@comark/react'
import math, { Math } from '@comark/react/plugins/math'
import mermaid, { Mermaid } from '@comark/react/plugins/mermaid'
import highlight from '@comark/react/plugins/highlight'
import githubLight from '@shikijs/themes/github-light'
import githubDark from '@shikijs/themes/github-dark'
import CodeBlock from './components/CodeBlock'
import Alert from './components/Alert'

export const DocsComark = defineComarkComponent({
  name: 'DocsComark',

  plugins: [
    math(),
    mermaid(),
    highlight({
      themes: {
        light: githubLight,
        dark: githubDark,
      },
    }),
  ],

  components: {
    Math,
    Mermaid,
    pre: CodeBlock,
    alert: Alert,
  },

  autoUnwrap: true,
  autoClose: true,
})
pages/docs/[slug].tsx
import { DocsComark } from '~/comark'
import { useLoaderData } from '@remix-run/react'

export default function DocPage() {
  const { page } = useLoaderData<typeof loader>()

  return (
    <article className="prose">
      <DocsComark>{page.content}</DocsComark>
    </article>
  )
}

Usage with Next.js App Router

comark.ts
'use client'

import { defineComarkComponent } from '@comark/react'
import math from '@comark/react/plugins/math'
import { Math } from '@comark/react/plugins/math'

export const DocsComark = defineComarkComponent({
  name: 'DocsComark',
  plugins: [math()],
  components: { Math },
})
app/docs/[slug]/page.tsx
import { DocsComark } from '@/components/comark'

export default async function Page({ params }: { params: { slug: string } }) {
  const content = await getDocContent(params.slug)

  return <DocsComark>{content}</DocsComark>
}

Multiple Configurations

Create different configurations for different contexts:

comark/index.ts
import { defineComarkComponent } from '@comark/react'
import math from '@comark/react/plugins/math'
import highlight from '@comark/react/plugins/highlight'
import githubLight from '@shikijs/themes/github-light'
import githubDark from '@shikijs/themes/github-dark'
import nord from '@shikijs/themes/nord'
import { Math } from '@comark/react/plugins/math'

// For documentation pages
export const DocsComark = defineComarkComponent({
  name: 'DocsComark',
  plugins: [
    math(),
    highlight({
      themes: { light: githubLight, dark: githubDark },
    }),
  ],
  components: { Math },
})

// For blog posts (simpler, no math)
export const BlogComark = defineComarkComponent({
  name: 'BlogComark',
  plugins: [
    highlight({
      themes: { light: nord, dark: nord },
    }),
  ],
})

// For comments (minimal features)
export const CommentComark = defineComarkComponent({
  name: 'CommentComark',
  autoUnwrap: false,
})

Custom Components

The <Comark> component supports custom components. Components receive props from the markdown and children as React children.

Creating a Custom Alert

components/Alert.tsx
interface AlertProps {
  type?: 'info' | 'warning' | 'error' | 'success'
  children: React.ReactNode
}

export default function Alert({ type = 'info', children }: AlertProps) {
  return (
    <div className={`alert alert-${type}`} role="alert">
      {children}
    </div>
  )
}

Usage in markdown:

::alert{type="warning"}
This is a warning message!
::

Component with Complex Props

components/DataTable.tsx
interface DataTableProps {
  columns?: string[]    // From {:columns='["Name","Age"]'}
  sortable?: boolean    // From {sortable}
  children: React.ReactNode
}

export default function DataTable({ columns = [], sortable, children }: DataTableProps) {
  return (
    <table>
      {columns.length > 0 && (
        <thead>
          <tr>
            {columns.map((col, i) => (
              <th key={i}>{col}</th>
            ))}
          </tr>
        </thead>
      )}
      <tbody>{children}</tbody>
    </table>
  )
}

Usage in markdown:

::data-table{:columns='["Name", "Age"]' sortable}
Table content here
::

Overriding HTML Elements

Override default element rendering:

components/Heading.tsx
interface HeadingProps {
  __node?: any
  id?: string
  children: React.ReactNode
}

export default function Heading({ __node, id, children }: HeadingProps) {
  const Tag = __node?.[0] || 'h2'

  return (
    <Tag id={id} className="heading">
      {id && <a href={`#${id}`} className="anchor">#</a>}
      {children}
    </Tag>
  )
}
App.tsx
const components = {
  h1: Heading,
  h2: Heading,
  h3: Heading,
}

<Comark components={components}>{content}</Comark>

Props Conversion

React renderer automatically converts attributes:

MarkdownReact
{class="foo"}className="foo"
{tabindex="0"}tabIndex={0}
{style="color: red"}style={{ color: 'red' }}
{:count="5"}count={5} (number)
{:data='{"key":"val"}'}data={{ key: "val" }} (object)

Performance Tips

  1. Use Comark for simple cases - It handles parsing internally and re-parses efficiently on content changes.
  2. Memoize component mappings:
App.tsx
// Good - defined outside component
const components = { alert: Alert, card: Card }

// Or use useMemo
const components = useMemo(() => ({ alert: Alert }), [])
  1. Use React.memo for custom components:
components/Alert.tsx
export default React.memo(function Alert({ type, children }) {
  return <div className={`alert-${type}`}>{children}</div>
})

Next Steps

Copyright © 2026