In the Node.js world are huge number of different libraries for coloring text in the terminal. Every library write that "I am the best", "I am fastest", "I'm smallest", etc.
Today the most popular library and de facto standard is the chаlk. The chalk
has rich functionality, is fast but not ideal. It lacks some useful features.
Let's explore this zoo and compare the availability of the most useful features.
Library ______________ - name - code size - named import |
Naming colors | ANSI 256 colors |
True- color |
Chained syntax |
Nested template strings |
New Line |
Supports CLI params |
---|---|---|---|---|---|---|---|
colors.js 18.1KB ❌ named import
|
non-standard16 colors |
❌ | ❌ | ✅ | ❌ | ✅ | onlyFORCE_COLOR --no-color --color
|
colors-cli 8.6KB ❌ named import
|
non-standard16 colors |
✅ | ❌ | ✅ | ❌ | ✅ | only--no-color --color
|
cli-color ❌ named import
|
standard16 colors |
✅ | ❌ | ✅ | ❌ | ❌ | onlyNO_COLOR
|
ansi-colors 5.8KB ❌ named import
|
standard16 colors |
❌ | ❌ | ✅ | ❌ | ✅ | onlyFORCE_COLOR
|
colorette 3.3KB ✅ named import
|
standard16 colors |
❌ | ❌ | ❌ | ❌ | ❌ |
NO_COLOR FORCE_COLOR --no-color --color
|
picocolors 2.6KB ❌ named import
|
standard8 colors |
❌ | ❌ | ❌ | ❌ | ❌ |
NO_COLOR FORCE_COLOR --no-color --color
|
kleur 2.7KB ✅ named import
|
standard8 colors |
❌ | ❌ | ✅ | ❌ | ❌ | onlyNO_COLOR FORCE_COLOR
|
chalk 15KB ❌ named import
|
standard16 colors |
✅ | ✅ | ✅ | ❌ | ✅ |
NO_COLOR FORCE_COLOR --no-color --color
|
ansis 3.2KB ✅ named import
|
standard16 colors |
✅ | ✅ | ✅ | ✅ | ✅ |
NO_COLOR FORCE_COLOR --no-color --color
|
Smallest code size
This is the size of pure JS code from the node module, that is runned. This does not include tests, documentation or other files. The smaller is the imported code, the faster it will be loaded and executed, in theory.
Here are the smallest libs: picocolors (2.6KB), kleur (2.7KB), ansis (3.2KB) and colorette (3.3KB), the rest are at least double larger.
But size doesn't always matter. For example, the smallest picocolors
has a minimum of features, while the equivalent by size ansis
has a maximum of useful features.
Naming and number of supported colors
There are libraries with standard color names, similar to chalk (red
, redBright
), and non-standard ones, such as colors.js
(brightRed
) or colors-cli
(red_bbt
).
Only chalk
and ansis
support truecolor, others support for 16 or 256 colors, and smallest picocolors
and kleur
only support 8 base colors.
The ANSI 256 colors:
Code range | Description |
---|---|
0 - 7 | base colors |
8 - 15 | bright colors |
16 - 231 | 6 × 6 × 6 cube |
232 - 255 | grayscale |
Using the chalk
or ansis
you can colorize the text with truecolor.
import {hex, rgb} from 'ansis/colors';
// ruby color with bold style
hex('#E0115F').bold`Ruby`;
rgb(224, 17, 95).bold`Ruby`;
import chalk from 'chalk';
// ruby color with bold style
chalk.hex('#E0115F').bold`Ruby`;
chalk.rgb(224, 17, 95).bold`Ruby`;
Named import
Without a named import, you use the library name to access the colors, e.g.:
import color from 'some-lib.js';
let out = color.blue('file.js') + red(' not found!');
Using the named import, you directly import only the colors you use, e.g.:
import {red, blue} from 'some-lib.js';
let out = blue('file.js') + red(' not found!');
// OR using template literals
let out = `${blue('file.js')} ${red('not found!')}`
The colorette
support only named import, kleur
and ansis
supports both named import and normal import, others does not have a named import.
Chained syntax
Any color and style can be combined in any order.
Almost all libraries support chained syntax, except for smallest picocolors
and colorette
.
import {blue, bold} from 'some-lib.js';
let out = blue.underline('file.js') + bold.red(' not found!');
Nested template strings
In some complex cases you can use the nested template strings.
This feature is supported by ansis
only.
import { red, green, blue } from 'ansis/colors';
let out = red`R ${green`G ${blue.bold`B`} G`} R`;
Using the chunk
it would look like:
import chalk from 'chalk';
let out = chalk.red`R` +
chalk.green`G` +
chalk.blue.bold`B` +
chalk.green`G` +
chalk.red`R`;
New lines
Not all libraries support correct style break at the end of line
, see the table above. For example, the smallest picocolors
, colorette
and kleur
cannot do this.
import { bgGreen } from 'ansis/colors';
bgGreen`\nAnsis\nNew Line\nNext New Line\n`;
Benchmark
Setup and run
git clone https://github.com/webdiscus/ansis.git
cd ./ansis/bench
npm i
npm run bench
Tested on
MacBook Pro 16" M1 Max 64GB
macOS Monterey 12.1
Node.js v16.13.1
TerminaliTerm2
Colorette bench
c.red(`${c.bold(`${c.cyan(`${c.yellow('yellow')}cyan`)}`)}red`);
+ colorette 4,572,582 ops/sec very fast
picocolors 3,841,124 ops/sec very fast
ansis 2,669,734 ops/sec fast
kleur/colors 2,281,415 ops/sec fast
chalk 2,287,146 ops/sec fast
kleur 2,228,639 ops/sec fast
ansi-colors 1,265,615 ops/sec slow
colors.js 1,158,572 ops/sec slow
cli-color 470,320 ops/sec too slow
colors-cli 109,811 ops/sec too slow
Base colors
const colors = ['black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white'];
colors.forEach((color) => c[color]('foo'));
+ picocolors 8,265,628 ops/sec very fast
ansis 6,197,754 ops/sec fast
kleur 5,455,121 ops/sec fast
chalk 4,428,884 ops/sec fast
kleur/colors 2,074,111 ops/sec slow
colorette 1,874,506 ops/sec slow
ansi-colors 1,010,628 ops/sec slow
colors.js 640,101 ops/sec too slow
cli-color 305,690 ops/sec too slow
colors-cli 104,962 ops/sec too slow
Chained styles
colors.forEach((color) => c[color].bold.underline.italic('foo'));
+ ansis 5,515,868 ops/sec very fast
chalk 1,234,573 ops/sec fast
kleur 514,035 ops/sec slow
ansi-colors 158,921 ops/sec too slow
cli-color 144,837 ops/sec too slow
colors.js 138,219 ops/sec too slow
colors-cli 52,732 ops/sec too slow
kleur/colors (not supported)
colorette (not supported)
picocolors (not supported)
Nested calls
colors.forEach((color) => c[color](c.bold(c.underline(c.italic('foo')))));
+ picocolors 942,592 ops/sec very fast
colorette 695,350 ops/sec fast
kleur 648,195 ops/sec fast
kleur/colors 561,111 ops/sec fast
ansis 558,575 ops/sec fast
chalk 497,292 ops/sec fast
ansi-colors 260,316 ops/sec slow
colors.js 166,425 ops/sec slow
cli-color 65,561 ops/sec too slow
colors-cli 13,800 ops/sec too slow
Nested styles
c.red(
`a red ${c.white('white')} red ${c.red('red')} red ${c.cyan('cyan')} red ${c.black('black')} red ${c.red(
'red'
)} red ${c.green('green')} red ${c.red('red')} red ${c.yellow('yellow')} red ${c.blue('blue')} red ${c.red(
'red'
)} red ${c.magenta('magenta')} red ${c.red('red')} red ${c.red('red')} red ${c.red('red')} red ${c.red(
'red'
)} red ${c.red('red')} red ${c.red('red')} red ${c.red('red')} red ${c.red('red')} red ${c.red('red')} red ${c.red(
'red'
)} red ${c.red('red')} red ${c.red('red')} red ${c.red('red')} red ${c.red('red')} red ${c.red('red')} red ${c.green(
'green'
)} red ${c.red('red')} red ${c.red('red')} red ${c.red('red')} red ${c.red('red')} red ${c.red('red')} red ${c.red(
'red'
)} red ${c.red('red')} red ${c.red('red')} red ${c.red('red')} red ${c.red('red')} red ${c.magenta(
'magenta'
)} red ${c.red('red')} red ${c.red('red')} red ${c.cyan('cyan')} red ${c.red('red')} red ${c.red(
'red'
)} red ${c.yellow('yellow')} red ${c.red('red')} red ${c.red('red')} red ${c.red('red')} red ${c.red(
'red'
)} red ${c.red('red')} red ${c.red('red')} red ${c.red('red')} message`
);
+ picocolors 243,975 ops/sec very fast
colorette 243,139 ops/sec very fast
kleur/colors 234,132 ops/sec very fast
kleur 221,446 ops/sec very fast
ansis 211,868 ops/sec very fast
chalk 189,960 ops/sec fast
ansi-colors 121,451 ops/sec slow
colors.js 89,633 ops/sec too slow
cli-color 41,657 ops/sec too slow
colors-cli 14,264 ops/sec too slow
Deep nested styles
c.green(
`green ${c.cyan(
`cyan ${c.red(
`red ${c.yellow(
`yellow ${c.blue(
`blue ${c.magenta(`magenta ${c.underline(`underline ${c.italic(`italic`)} underline`)} magenta`)} blue`
)} yellow`
)} red`
)} cyan`
)} green`
);
+ colorette 1,131,757 ops/sec very fast
picocolors 1,002,649 ops/sec very fast
ansis 882,220 ops/sec fast
chalk 565,965 ops/sec fast
kleur/colors 478,547 ops/sec fast
kleur 464,004 ops/sec fast
colors.js 451,592 ops/sec fast
ansi-colors 362,733 ops/sec slow
cli-color 213,441 ops/sec slow
colors-cli 40,340 ops/sec too slow
HEX colors
Only two libraries supported truecolor methods: ansis
and chalk
c.hex('#FBA')('foo');
+ ansis 4,944,572 ops/sec very fast
chalk 2,891,684 ops/sec fast
colors.js (not supported)
colorette (not supported)
picocolors (not supported)
cli-color (not supported)
colors-cli (not supported)
ansi-colors (not supported)
kleur/colors (not supported)
kleur (not supported)
Conclusion
There is definitely no fastest, smallest and most functional library.
But we can identify 3 main leaders:
- picocolors smallest and fastest for simple use cases
- chalk most popular and fast for commonly use cases
- ansis small, very fast (faster than chalk) and powerful for simple and complex use cases
Don't be afraid to be modern and experiment with new things.
P.S.
The ansis is not as popular (yet) as chalk, but give a chance to a new, modern, powerful library. Just try this in your new project.
import { black, cyan, green, inverse, red } from 'ansis/colors';
let out = green`Hello ${inverse`ANSI`} World!
${black.bgYellow`Warning:`} ${cyan`file.js`} ${red`not found!`}`;
console.log(out);
The output:
View and edit this example in browser:
Top comments (1)
That is a very comprehensive comparison! Shout out to Sindre's other color library that competes with picocolors, github.com/sindresorhus/yoctocolors