Styling console text in Node.js

[2025-05-15] dev, javascript, nodejs
(Ad, please don’t block)

In this blog post, we explore how we can style text that we log to the console in Node.js.

Some of the examples use a Unix shell but most of the code should also work on Windows.

ANSI escape sequences  

If we want to style text we print to a terminal, we use ANSI escape sequences. Quoting Wikipedia:

ANSI escape sequences are a standard for in-band signaling to control cursor location, color, font styling, and other options on video text terminals and terminal emulators. Certain sequences of bytes, most starting with an ASCII escape character and a bracket character, are embedded into text. The terminal interprets these sequences as commands, rather than text to display verbatim.

ANSI sequences were introduced in the 1970s to replace vendor-specific sequences and became widespread in the computer equipment market by the early 1980s. Although hardware text terminals have become increasingly rare in the 21st century, the relevance of the ANSI standard persists because a great majority of terminal emulators and command consoles interpret at least a portion of the ANSI standard.

The Select Graphic Rendition (SGR) control sequence sets display attributes  

SGR is an ANSI escape sequence that sets display attributes:

SGR        = "\x1B[" ATTRIBUTES? "m" ;
ATTRIBUTES = ATTRIBUTE (";" ATTRIBUTE)* ;
ATTRIBUTE  = DECIMAL_DIGIT+

0x1B is the escape control character.

These are two examples of attributes:

  • 1: Bold or increased intensity
  • 22: Normal intensity (neither bold nor faint)

Therefore, the following statement switches to bold, displays “TEXT” and then switches back to a normal font weight:

console.log('\x1B[1mTEXT\x1B[22m')

Node.js: util.styleText()  

Node.js has a built-in function for creating strings with SGR control sequences:

util.styleText(format, text, options?)

format is a single string with a “modifier” (a name for one of the SGR attributes) or an Array of modifiers. These are a few examples of modifiers (complete list of modifiers):

  • reset
  • bold
  • italic
  • red (foreground)
  • bgGreen (background)

The following code demonstrates util.styleText():

> util.styleText('bold', 'TEXT')
'\x1B[1mTEXT\x1B[22m'

https://nodejs.org/api/util.html#utilstyletextformat-text-options

Creating a template tag for styling text  

The following TypeScript code demonstrates how we can define a template tag for bold text:

const b: TagFunction<string> = (templateStrings, ...substitutions) => (
  styleText('bold', String.raw(templateStrings, ...substitutions))
);

Alas, there is no built-in type TagFunction – we have to define it ourselves:

type TagFunction<R> = (
  templateStrings: TemplateStringsArray,
  ...substitutions: unknown[]
) => R;

The template tag is used like this:

console.log(`This is ${b`bold`} text`);

ANSI escape sequences are sometimes treated differently if stdout is not a terminal  

Detecting if stdout is a terminal  

We can use the property process.stdout.isTTY to detect if stdout is a terminal. The following shell commands demonstrate how that works:

% node -p 'process.stdout.isTTY'
true
% node -p 'process.stdout.isTTY' | cat
undefined
% echo | node -p 'process.stdout.isTTY'
true

“TTY” stands for teletypewriter (source).

util.styleText() omits display attributes if stdout is not a terminal  

Demonstration:

% node -p "JSON.stringify(util.styleText('bold', 'TEXT'))"
"\u001b[1mTEXT\u001b[22m"
% node -p "JSON.stringify(util.styleText('bold', 'TEXT'))" | cat
"TEXT"

Option validateStream lets us change that behavior:

% node -p "JSON.stringify(util.styleText('bold', 'TEXT', {validateStream: false}))" | cat
"\u001b[1mTEXT\u001b[22m"

We can also switch it off by setting the environment variable FORCE_COLOR to a value greater than 0:

% FORCE_COLOR=3 node -p "JSON.stringify(util.styleText('bold', 'TEXT'))" | cat
"\u001b[1mTEXT\u001b[22m"

We can even change that environment variable in code and it has the same effect:

process.env['FORCE_COLOR'] = '3';

The Node.js documentation has more information on FORCE_COLOR.

Display attributes and the shell command less  

By default, less does not interpret ANSI escape sequences and displays escaped characters:

% node -p "'\x1B[1mtext\x1B[22m'" | less

We can change that via the option -R:

% node -p "'\x1B[1mtext\x1B[22m'" | less -R