ANSI Terminal Rendering
The @comark/ansi package renders Markdown to ANSI-styled strings for terminal output. Install it separately:
Installation
pnpm add @comark/ansinpm install @comark/ansiyarn add @comark/ansibun add @comark/ansirenderAnsi()
The quickest way to parse Markdown and get an ANSI-styled string in one call.
Usage
import { renderAnsi } from '@comark/ansi'
const output = await renderAnsi(`
# 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 |
autoClose | boolean | true | Close incomplete Markdown and components before parsing |
autoUnwrap | boolean | true | Remove a single paragraph wrapper inside components |
linkify | boolean | true | Convert URL-like text into links |
registerDefaultPlugins | boolean | true | Register default plugins (frontmatter, html, alert, task-list, components, attributes) |
unwrap | boolean | string | string[] | false | Remove selected wrapper tags from the parsed document |
*Automatically set to false when the NO_COLOR env var is present.
renderAnsi() accepts all ParserOptions in addition to the ANSI renderer options above.
plugins
See ComarkPlugin for available plugins.
import { renderAnsi } from '@comark/ansi'
import shiki from '@comark/ansi/plugins/shiki'
const output = await renderAnsi('```typescript\nconsole.log("hello")\n```', {
plugins: [shiki()],
})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 { renderAnsi } from '@comark/ansi'
const output = await renderAnsi(`
::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 { renderAnsi } from '@comark/ansi'
const output = await renderAnsi(`
::status
All systems operational.
::
`, {
data: { env: 'production' },
components: {
status: async ([, , ...children], { render, data }) => {
return `[${data?.env}] ${await render(children)}`
},
},
})
// → [production] All systems operationalcreateAnsiRenderer()
Creates a reusable parse+render function. The underlying parser is initialized once and reused on every call, which is more efficient when rendering many documents.
Usage
import { createAnsiRenderer } from '@comark/ansi'
import shiki from '@comark/ansi/plugins/shiki'
const render = createAnsiRenderer({
plugins: [shiki()],
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 renderAnsi().
writeAnsi()
Parse and print markdown directly to stdout in one call.
Usage
import { writeAnsi } from '@comark/ansi'
await writeAnsi(`
# Hello World
This is **bold**, _italic_, and \`inline code\`.
> [!NOTE]
> @comark/ansi renders GitHub-style alerts with color.
`)Options
Pass options to configure the parser, renderer, or output destination using writer:
import { writeAnsi } from '@comark/ansi'
import math, { Math } from '@comark/ansi/plugins/math'
await writeAnsi('Inline $E = mc^2$', {
plugins: [math()],
components: { Math },
width: 100,
writer: (s) => process.stderr.write(s),
})They are the same options as renderAnsi() plus the writer?: (string: string) => void option.
createAnsiWriter()
Creates a reusable writer with pre-configured options. The underlying parser is initialized once and reused on every call, which is more efficient when writing many documents.
Usage
import { createAnsiWriter } from '@comark/ansi'
import math, { Math } from '@comark/ansi/plugins/math'
import shiki from '@comark/ansi/plugins/shiki'
const write = createAnsiWriter({
plugins: [math(), shiki()],
components: { Math },
width: 120,
})
// Reuse the same configured parser & writer
await write('# Document 1\n\n...')
await write('# Document 2\n\n...')Options
Same as writeAnsi().
renderAnsiFromDocument()
Render a pre-parsed MarkdownDocument to an ANSI string, with no parsing step. Use this when you already have a document and want to avoid re-parsing.
Integration
import { parseMarkdown } from 'comark'
import { renderAnsiFromDocument } from '@comark/ansi'
const document = await parseMarkdown(`
# Getting Started
This is a **bold** statement with a [link](https://example.com).
- Item 1
- Item 2
`)
const output = await renderAnsiFromDocument(document)
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 { createAnsiRenderer } from '@comark/ansi'
const renderAnsi = createAnsiRenderer({
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 { createAnsiWriter } from '@comark/ansi'
import shiki from '@comark/ansi/plugins/shiki'
const writeAnsi = createAnsiWriter({ plugins: [shiki()] })
await writeAnsi('```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 { createAnsiWriter } from '@comark/ansi'
import math, { Math } from '@comark/ansi/plugins/math'
const writeAnsi = createAnsiWriter({ plugins: [math()], components: { Math } })
await writeAnsi('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 { AnsiRendererOptions } from '@comark/ansi'
import { createAnsiRenderer } from '@comark/ansi'
import type { NodeHandler } from 'comark'
const components: Record<string, NodeHandler> = {
badge: async ([, attrs, ...children], { render }) => {
return `[${String(attrs.type).toUpperCase()}] ${await render(children)}`
},
}
const options: AnsiRendererOptions = { colors: true, width: 100, components }
const renderAnsi = createAnsiRenderer(options)