Render Comark in React
The <Comark> component is the simplest way to render markdown in React. Pass markdown content directly and it handles parsing and rendering automatically.
<Comark> component does not require <Suspense> wrapping. Async plugins are handled internally.Basic Usage
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
| Prop | Type | Default | Description |
|---|---|---|---|
children | React.ReactNode | - | Markdown content to parse and render |
markdown | string | '' | Alternative to children - markdown content as prop |
options | ParseOptions | {} | Parser options (autoUnwrap, autoClose, etc.) |
plugins | ComarkPlugin[] | [] | Array of plugins (highlight, emoji, toc, etc.) |
components | Record<string, ComponentType> | {} | Custom React component mappings |
componentsManifest | (name: string) => Promise<Component> | undefined | Dynamic component resolver function |
streaming | boolean | false | Enable streaming mode |
caret | boolean | { class: string } | false | Append caret to last text node |
className | string | undefined | CSS class for wrapper element |
Using the markdown Prop
Instead of using children, you can pass markdown content via the markdown prop:
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:
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:
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
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
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 -->:
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
<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
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
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
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
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
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:
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
- Centralized Configuration: Define options once, use everywhere
- Consistent Behavior: Same plugins and components across your app
- Type Safety: Full TypeScript support
- Easy Overrides: Instance-level props can override defaults
- Better DevTools: Named components appear in React DevTools
Merging Behavior
When using the defined component, configurations are merged:
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
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,
})
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
'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 },
})
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:
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
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
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:
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>
)
}
const components = {
h1: Heading,
h2: Heading,
h3: Heading,
}
<Comark components={components}>{content}</Comark>
Props Conversion
React renderer automatically converts attributes:
| Markdown | React |
|---|---|
{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
- Use
Comarkfor simple cases - It handles parsing internally and re-parses efficiently on content changes. - Memoize component mappings:
// Good - defined outside component
const components = { alert: Alert, card: Card }
// Or use useMemo
const components = useMemo(() => ({ alert: Alert }), [])
- Use React.memo for custom components:
export default React.memo(function Alert({ type, children }) {
return <div className={`alert-${type}`}>{children}</div>
})
Next Steps
- Streaming - Real-time incremental parsing for AI chat
- HTML Rendering - Framework-free HTML/Markdown output
- Vue Rendering - Vue integration
- Components Syntax - Block and inline component syntax
- Parse API - Parsing options and error handling