---
title: "ANSI Terminal Rendering"
description: "Render Markdown as styled terminal output using ANSI escape codes, perfect for CLIs, scripts, and developer tooling."
canonical_url: "https://comark.dev/rendering/ansi"
---
# 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

<code-group>
```bash [pnpm]
pnpm add @comark/ansi
```


```bash [npm]
npm install @comark/ansi
```


```bash [yarn]
yarn add @comark/ansi
```


```bash [bun]
bun add @comark/ansi
```
</code-group>

## `renderAnsi()`

The quickest way to parse Markdown and get an ANSI-styled string in one call.

### Usage

~~~typescript
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)
~~~

```text [Terminal]
# Getting Started   ← bold + underline

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

• Item 1
• Item 2
```

### Options

| Option                                     | Type                            | Default     | Description                                                                                        |
| ------------------------------------------ | ------------------------------- | ----------- | -------------------------------------------------------------------------------------------------- |
| [`plugins`](#render-options-plugins)       | `ComarkPlugin[]`                | `[]`        | Array of plugins                                                                                   |
| [`components`](#render-options-components) | `Record<string, fn>`            | `{}`        | Custom component renderers                                                                         |
| [`data`](#render-options-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`](https://comark.dev/reference/parse#options) in addition to the ANSI renderer options above.

#### `plugins`

See [ComarkPlugin](https://comark.dev/plugins) for available plugins.

```ts
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:

~~~ts
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:

~~~ts
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

```typescript
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()`](#renderansi-options).

---

## `printAnsi()`

Parse and print markdown directly to `stdout` in one call.

<note>
`printAnsi()` replaces `writeAnsi()`, which is deprecated and will be removed in the next major version.
</note>

### Usage

~~~ts
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`:

```ts
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()`](#renderansi-options) 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.

<note>
`createAnsiPrinter()` replaces `createAnsiWriter()`, which is deprecated and will be removed in the next major version.
</note>

#### Usage

```ts
import { createAnsiPrinter } from '@comark/ansi'
import math, { Math } from '@comark/ansi/plugins/math'
import shiki from '@comark/ansi/plugins/shiki'

const print = createAnsiPrinter({
  plugins: [math(), shiki()],
  components: { Math },
  width: 120,
})

// Reuse the same configured parser & printer
await print('# Document 1\n\n...')
await print('# Document 2\n\n...')
```

#### Options

Same as [`printAnsi()`](#printansi-options).

---

## `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

~~~typescript
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`](#render-options-components) | `Record<string, fn>`  | `{}`    | Custom component renderers                   |
| [`data`](#render-options-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:

```typescript
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:

```markdown
> [!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:

```typescript
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:

```typescript
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

```typescript
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)
```

---

- [Plugins](https://comark.dev/plugins)
- [Streaming API](https://comark.dev/reference/auto-close)


## Sitemap

See the full [sitemap](https://comark.dev/sitemap.md) for all pages.
