ANSI Terminal Rendering

Render Markdown as styled terminal output using ANSI escape codes, perfect for CLIs, scripts, and developer tooling.

The @comark/ansi package renders Markdown to ANSI-styled strings for terminal output. Install it separately:

Installation

pnpm add @comark/ansi

renderAnsi()

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)
Terminal
# Getting Started   ← bold + underline

This is a bold statement with a link (https://example.com).

• Item 1
• Item 2

Options

OptionTypeDefaultDescription
pluginsComarkPlugin[][]Array of plugins
componentsRecord<string, fn>{}Custom component renderers
dataRecord<string, any>undefinedData passed to component renderers
colorsbooleantrue*Emit ANSI escape codes
widthnumber80Terminal width for HR and code block headers
autoClosebooleantrueClose incomplete Markdown and components before parsing
autoUnwrapbooleantrueRemove a single paragraph wrapper inside components
linkifybooleantrueConvert URL-like text into links
registerDefaultPluginsbooleantrueRegister default plugins (frontmatter, html, alert, task-list, components, attributes)
unwrapboolean | string | string[]falseRemove 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 passed

data

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 operational

createAnsiRenderer()

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

OptionTypeDefaultDescription
componentsRecord<string, fn>{}Custom component renderers
dataRecord<string, any>Data passed to component renderers
colorsbooleantrue*Emit ANSI escape codes
widthnumber80Terminal 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 highlighted

Math

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)