ANSI Terminal Rendering
The @comark/ansi package renders Comark AST to ANSI-styled strings for terminal output. Install it separately:
Installation
pnpm add @comark/ansinpm install @comark/ansiyarn add @comark/ansibun add @comark/ansirender()
The quickest way to parse markdown and get an ANSI-styled string in one call.
Usage
import { render } from '@comark/ansi'
const output = await render(`
# Getting Started
This is a **bold** statement with a [link](https://example.com).
- Item 1
- Item 2
`)
process.stdout.write(output)# Getting Started ← bold + underline
This is a bold statement with a link (https://example.com).
• Item 1
• Item 2Options
| Option | Type | Default | Description |
|---|---|---|---|
plugins | ComarkPlugin[] | [] | Array of plugins |
components | Record<string, fn> | {} | Custom component renderers |
data | Record<string, any> | undefined | Data passed to component renderers |
colors | boolean | true* | Emit ANSI escape codes |
width | number | 80 | Terminal width for HR and code block headers |
*Automatically set to false when the NO_COLOR env var is present.
plugins
See ComarkPlugin for available plugins.
import { render } from '@comark/ansi'
import highlight from '@comark/ansi/plugins/highlight'
const output = await render('```typescript\nconsole.log("hello")\n```', {
plugins: [highlight()],
})components
Map component names to async render functions. Each function receives the element as [tag, attrs, ...children] and a context with render to process nested content:
import { render } from '@comark/ansi'
const output = await render(`
::badge{type="success"}
Build passed
::
`, {
components: {
badge: async ([, attrs, ...children], { render }) => {
return `[${String(attrs.type).toUpperCase()}] ${await render(children)}`
},
},
})
// → [SUCCESS] Build passeddata
Pass external data to every component renderer via the context object:
import { render } from '@comark/ansi'
const output = await render(`
::status
All systems operational.
::
`, {
data: { env: 'production' },
components: {
status: async ([, , ...children], { render, data }) => {
return `[${data?.env}] ${await render(children)}`
},
},
})createRender()
Creates a reusable parse+render function. The underlying parser is initialized once and reused on every call — more efficient when rendering many documents.
Usage
import { createRender } from '@comark/ansi'
import highlight from '@comark/ansi/plugins/highlight'
const render = createRender({
plugins: [highlight()],
width: 120,
})
// Reuse the same configured parser
const out1 = await render('# Document 1\n\n...')
const out2 = await render('# Document 2\n\n...')Options
Same as render().
log()
Parse and print markdown directly to stdout in one call.
import { log } from '@comark/ansi'
await log(`
# Hello World
This is **bold**, _italic_, and \`inline code\`.
> [!NOTE]
> @comark/ansi renders GitHub-style alerts with color.
`)Pass options to configure the parser, renderer, or output destination:
import { log } from '@comark/ansi'
import math, { Math } from '@comark/ansi/plugins/math'
await log('Inline $E = mc^2$', {
plugins: [math()],
components: { Math },
width: 100,
write: (s) => process.stderr.write(s),
})createLog()
Creates a reusable log function with pre-configured options. The underlying parser is initialized once and reused on every call — more efficient when logging many documents.
Usage
import { createLog } from '@comark/ansi'
import math, { Math } from '@comark/ansi/plugins/math'
import highlight from '@comark/ansi/plugins/highlight'
const log = createLog({
plugins: [math(), highlight()],
components: { Math },
width: 120,
})
// Reuse the same configured parser
await log('# Document 1\n\n...')
await log('# Document 2\n\n...')Options
Same as log().
renderANSI()
Render a pre-parsed Comark tree to an ANSI string — no parsing step. Use this when you already have a parsed tree and want to avoid re-parsing.
Integration
import { parse } from 'comark'
import { renderANSI } from '@comark/ansi'
const tree = await parse(`
# Getting Started
This is a **bold** statement with a [link](https://example.com).
- Item 1
- Item 2
`)
const output = await renderANSI(tree)
process.stdout.write(output)Options
| Option | Type | Default | Description |
|---|---|---|---|
components | Record<string, fn> | {} | Custom component renderers |
data | Record<string, any> | — | Data passed to component renderers |
colors | boolean | true* | Emit ANSI escape codes |
width | number | 80 | Terminal width for HR and code block headers |
*Automatically set to false when the NO_COLOR env var is present.
Overriding Terminal Output
Pass native markdown tag names as keys in components to override how standard elements render in the terminal:
import { createRender } from '@comark/ansi'
const render = createRender({
components: {
h1: async ([, , ...children], { render }) => {
return `\x1b[1;4;35m★ ${await render(children)}\x1b[0m\n`
},
a: async ([, attrs, ...children], { render }) => {
const label = await render(children)
return `\x1b[36m${label}\x1b[0m (\x1b[2m${attrs.href}\x1b[0m)`
},
},
})Syntax Support
Headings
Headings are styled by level — bold + underline for h1, with distinct colors per level down to h6.
GitHub Alerts
Blockquotes with [!TYPE] markers render as colored alerts matching GitHub's style:
> [!NOTE]
> Informational message.
> [!TIP]
> Helpful suggestion.
> [!IMPORTANT]
> Crucial information.
> [!WARNING]
> Potential risk.
> [!CAUTION]
> Danger ahead.Each type has its own color: NOTE → blue, TIP → green, IMPORTANT → magenta, WARNING → yellow, CAUTION → red.
Code Blocks
Code blocks show the language and filename in a header line. When the highlight plugin is used, tokens are rendered with true-color ANSI (\x1b[38;2;R;G;Bm) derived from Shiki's dark theme:
import { createLog } from '@comark/ansi'
import highlight from '@comark/ansi/plugins/highlight'
const log = createLog({ plugins: [highlight()] })
await log('```typescript [app.ts]\nconsole.log("hello")\n```')
// typescript app.ts
// console.log("hello") ← syntax highlightedMath
Math expressions from the math plugin render as colored LaTeX source — inline in yellow, block in magenta:
import { createLog } from '@comark/ansi'
import math, { Math } from '@comark/ansi/plugins/math'
const log = createLog({ plugins: [math()], components: { Math } })
await log('Inline $E = mc^2$ and block:\n\n$$\n\\frac{a}{b}\n$$')Tables
Tables render with box-drawing characters:
┌─────────────┬─────────┐
│ Feature │ Status │
├─────────────┼─────────┤
│ Headings │ ✅ │
│ Code blocks │ ✅ │
└─────────────┴─────────┘TypeScript Support
import type { ComarkElement, ComarkNode } from 'comark'
import { createRender } from '@comark/ansi'
type RenderContext = {
render: (nodes: ComarkNode[]) => Promise<string>
data?: Record<string, any>
}
type ComponentRenderer = (element: ComarkElement, ctx: RenderContext) => Promise<string>
const components: Record<string, ComponentRenderer> = {
badge: async ([, attrs, ...children], { render }) => {
return `[${String(attrs.type).toUpperCase()}] ${await render(children)}`
},
}
const render = createRender({ components })