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().
printAnsi()
Parse and print markdown directly to stdout in one call.
printAnsi() replaces writeAnsi(), which is deprecated and will be removed in the next major version.Usage
import { printAnsi } from '@comark/ansi'
await printAnsi(`
# 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 { printAnsi } from '@comark/ansi'
import math, { Math } from '@comark/ansi/plugins/math'
await printAnsi('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.
createAnsiPrinter()
Creates a reusable printer with pre-configured options. The underlying parser is initialized once and reused on every call, which is more efficient when printing many documents.
createAnsiPrinter() replaces createAnsiWriter(), which is deprecated and will be removed in the next major version.Usage
import { createAnsiPrinter } from '@comark/ansi'
import math, { Math } from '@comark/ansi/plugins/math'
import shiki from '@comark/ansi/plugins/shiki'
const write = createAnsiPrinter({
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 printAnsi().
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 shiki plugin is used, tokens are rendered with true-color ANSI (\x1b[38;2;R;G;Bm) derived from Shiki's dark theme:
import { createAnsiPrinter } from '@comark/ansi'
import shiki from '@comark/ansi/plugins/shiki'
const printAnsi = createAnsiPrinter({ plugins: [shiki()] })
await printAnsi('```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 { createAnsiPrinter } from '@comark/ansi'
import math, { Math } from '@comark/ansi/plugins/math'
const printAnsi = createAnsiPrinter({ plugins: [math()], components: { Math } })
await printAnsi('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)