Custom Renderer for marked allowing for printing Markdown to the Terminal. Supports pretty tables, syntax highlighting, and overriding all colors and styles.
Could for instance be used to print usage information.
pnpm add marked marked-terminalRequires a Node.js LTS version (>= 22.12) and marked >= 13. TypeScript types are included.
import { marked } from 'marked';
import { markedTerminal } from 'marked-terminal';
marked.use(markedTerminal([options][, highlightOptions]));
console.log(marked.parse('# Hello \n This is **markdown** printed in the `terminal`'));The package is ESM-only. It can still be loaded with require() from CommonJS,
which is supported natively from Node.js 22.12:
const { marked } = require('marked');
const { markedTerminal } = require('marked-terminal');This will produce the following:
marked-terminal also comes with a command line tool, mt, for rendering
Markdown files or piped input directly from bash, PowerShell and other shells:
pnpm dlx marked-terminal README.md # without installing
pnpm add -g marked marked-terminal
mt README.md
cat CHANGELOG.md | mt --width 60The CLI wraps text and tables to the terminal width (at most 100 columns) and
indents with 2 spaces. Colors are turned off when output isn't a terminal or
NO_COLOR is set. Several files can be passed at once, and - reads from
stdin.
Usage: mt [options] [file ...]
Render Markdown to the terminal. Reads from stdin when no file is given,
or when file is "-".
Options:
-f, --file <path> Markdown file to render (same as passing it as an argument)
-w, --width <columns> Width to wrap text and tables at
(default: terminal width, at most 100)
--no-reflow Don't wrap text and tables to the width
--color Force colors, even when not writing to a terminal
--no-color Disable colors (also respects NO_COLOR)
--no-emoji Don't replace :emoji: codes with emojis
--no-section-prefix Don't prefix headings with #
--no-images Show images as text instead of drawing them
--remote-images Download and draw http(s) images
-h, --help Show this help
-v, --version Show version
Examples:
mt README.md
cat CHANGELOG.md | mt --width 60
pnpm dlx marked-terminal README.md
The CLI draws images in the terminal, resolving paths relative to the
Markdown file (or the current directory for stdin). Remote images are only
downloaded with --remote-images. When output isn't a terminal, images are
printed as text. See Images.
Set images to draw images in the terminal instead of printing them as
:
marked.use(markedTerminal({ images: true }));
console.log(await marked.parse(''));Images are loaded asynchronously, so marked.parse returns a promise when
images is set. How images are drawn depends on the terminal:
| Terminal | Images |
|---|---|
| iTerm2, WezTerm, VS Code, Konsole, mintty, Rio | Full resolution (iTerm2 protocol) |
| Kitty, Ghostty, Warp | Full resolution for PNGs (Kitty protocol), others pixelated |
| Other terminals, tmux and screen | Pixelated, with colored half blocks |
| Output that isn't a terminal | Printed as text, as without images |
Try it in your terminal with node example/images.ts, optionally passing
kitty, iterm2 or ansi to force a protocol.
Pixelated images are drawn by terminal-image, which supports PNG, JPEG, GIF and BMP. Other formats, and images that can't be loaded, are printed as text.
images can also be an object of options:
markedTerminal({
images: {
// Directory relative image paths are resolved from (default: cwd)
baseDir: '/path/to/docs',
// Download http(s) images. Off by default, so rendering untrusted
// Markdown doesn't make network requests.
remote: false,
// Largest image to load, in bytes (default: 10 MB)
maxBytes: 10 * 1024 * 1024,
// Maximum width in columns (default: the output width)
width: 60,
// Force 'kitty', 'iterm2' or 'ansi' (pixelated) instead of detecting it
protocol: undefined
}
});Code blocks are syntax highlighted using emphasize (highlight.js). You can override highlighting defaults by passing in settings as the second argument.
Having the following markdown input:
```js
var foo = function(bar) {
console.log(bar);
};
foo('Hello');
```
...we will convert it into terminal format:
console.log(marked.parse(exampleSource));This will produce the following:
markedTerminal(options?: TerminalRendererOptions, highlightOptions?: HighlightOptions): MarkedExtensionOptional. Used to override default styling. Every style is a plain
(text: string) => string function, so you can use node:util's styleText,
chalk, picocolors or your own functions.
Default values are:
import { styleText } from 'node:util';
const defaultOptions = {
// Colors
code: (s) => styleText('yellow', s),
blockquote: (s) => styleText(['gray', 'italic'], s),
html: (s) => styleText('gray', s),
heading: (s) => styleText(['green', 'bold'], s),
firstHeading: (s) => styleText(['magenta', 'underline', 'bold'], s),
hr: (s) => styleText('reset', s),
listitem: (s) => styleText('reset', s),
table: (s) => styleText('reset', s),
paragraph: (s) => styleText('reset', s),
strong: (s) => styleText('bold', s),
em: (s) => styleText('italic', s),
codespan: (s) => styleText('yellow', s),
del: (s) => styleText(['dim', 'gray', 'strikethrough'], s),
link: (s) => styleText('blue', s),
href: (s) => styleText(['blue', 'underline'], s),
text: (s) => s,
// Formats the bulletpoints and numbers for lists
list: function (body, ordered, indent) {/* ... */},
// Reflow and print-out width
width: 80, // only applicable when reflow is true
reflowText: false, // also shrinks tables wider than `width` (unless tableOptions.colWidths is set)
// Should it prefix headers?
showSectionPrefix: true,
// Whether or not to undo marked escaping
// of enitities (" -> " etc)
unescape: true,
// Whether or not to show emojis
emoji: true,
// Options passed to cli-table3
tableOptions: {},
// The size of tabs in number of spaces or as tab characters
tab: 4, // examples: 4, 2, \t, \t\t
image: function (href, title, text) {}, // function for overriding the default image handling.
// Render links as clickable terminal hyperlinks instead of `text (url)`.
// Detected from stdout when not set.
hyperlinks: undefined,
// Draw images in the terminal, see Images above
images: false
};Colors are disabled when NO_COLOR is set or stdout doesn't support colors,
and can be forced with FORCE_COLOR.
import { styleText } from 'node:util';
marked.use(
markedTerminal({
codespan: (s) => styleText(['underline', 'magenta'], s)
})
);interface HighlightOptions {
// Map of highlight.js classes (without `hljs-` prefix) to style functions.
// Defaults to `defaultSheet`, a theme matching marked-terminal v7.
sheet?: Record<string, (text: string) => string>;
// Languages to consider when auto-detecting code without a language tag.
subset?: string[];
}import { defaultSheet, markedTerminal } from 'marked-terminal';
marked.use(
markedTerminal({}, { sheet: { ...defaultSheet, keyword: (s) => styleText('magenta', s) } })
);See more examples
pnpm install
pnpm test # vitest, including type tests
pnpm lint # biome
pnpm typecheck
pnpm build # tsdown -> dist/The examples in example/ are TypeScript and import straight from
src/, so there's no build step. Node runs .ts files directly through its
built-in type stripping (unflagged since Node 22.18 / 23.6):
node example/demo.ts # tour of most features
node example/reflow.ts # text reflow at a fixed width
node example/usage.ts # CLI usage/help output
node example/print.ts # render this README
node example/print.ts path/to.md # render any Markdown fileEdits to src/ show up on the next run, which makes the examples handy for
checking renderer changes by eye.
- ink-markdown - Markdown component for Ink

