5 min read
Bjørn Wikkeling
Share:

Barva 1.3, truecolor, level detection, and proper types

Barva 1.3 brings 24-bit colour, 256-palette support, automatic downgrade, a richer modifier set, and fully typed chaining, while keeping the tagged-template API tiny and tree-shakable.

I just released version 1.3 of barva , my tiny tagged-template terminal colour npm library. It’s still zero-dependency and tree-shakable, but I’ve given it a lot more to do out of the box.

Truecolor and the 256 palette

Up until 1.2 you only had the 16 basic ANSI colours and their bright variants to play with. As of 1.3, I’ve opened up the full range:

1import { rgb, hex, ansi256, bgHex, bold } from 'barva';
2
3console.log(rgb(255, 128, 0)`orange`);
4console.log(hex('#ff80c0')`pink`);
5console.log(ansi256(196)`bright red`);
6
7// All chainable with modifiers and basic colours
8console.log(bold.rgb(0, 200, 180).bgHex('#101820')`teal on navy`);

The bit I’m happiest about, if your terminal doesn’t advertise truecolor support, barva automatically downgrades the sequence to the best available palette, 256 or classic 16, using the standard cube and grayscale-ramp math. Your code doesn’t have to care about terminal capabilities.

Custom colour aliases

Because every factory call (rgb, hex, ansi256, …) and every chain access (red.bold, bold.bgYellow, …) returns a reusable colorizer, you can define your own palette with nothing more than a variable:

 1import { rgb, hex, red, bold, ansi256 } from 'barva';
 2
 3// Colour aliases
 4const orange    = rgb(255, 128, 0);
 5const brandPink = hex('#ff80c0');
 6const highlight = ansi256(196);
 7
 8// Style aliases, full chains work the same way
 9const error   = red.bold.underline;
10const warn    = hex('#ffa500').bold;
11const success = bold.rgb(0, 200, 120);
12
13console.log(orange`this is orange`);
14console.log(error`something went wrong`);
15console.log(warn`heads up, ${42} retries left`);
16console.log(success`done`);

Two bonuses come from the internal cache I added, calling rgb(255, 128, 0) twice from different modules returns the same instance, so scattering these around your codebase is free; and any alias is still a full colorizer, so you can keep chaining it (orange.bold`...`).

Levels instead of on/off

The old setEnabled() boolean is still there, but I’ve introduced a proper colour-level concept:

 1import { getLevel, setLevel, ColorLevel } from 'barva';
 2
 3// ColorLevel constants:
 4//   ColorLevel.None      = 0  (no colour)
 5//   ColorLevel.Basic     = 1  (16 colours)
 6//   ColorLevel.Ansi256   = 2  (256 colours)
 7//   ColorLevel.TrueColor = 3  (24-bit)
 8
 9getLevel();                     // returns 0 | 1 | 2 | 3
10setLevel(ColorLevel.TrueColor); // force truecolor output
11setLevel(undefined);            // re-run environment detection

A ColorLevel constant is also exported, so you don’t have to memorise the numbers. The matching ColorLevel TypeScript type is still the literal union 0 | 1 | 2 | 3, so setLevel(ColorLevel.TrueColor) type-checks exactly like setLevel(3).

To work out which level to use, barva checks a handful of standard environment variables (NO_COLOR, FORCE_COLOR, TERM, COLORTERM, WT_SESSION, TERMINAL_EMULATOR) and recognises the built-in terminals shipped with the entire JetBrains IDE family (IntelliJ IDEA, WebStorm, PyCharm, PhpStorm, RubyMine, CLion, GoLand, Rider, DataGrip, Android Studio, and the rest), as well as VS Code’s built-in terminal. They all get upgraded to truecolor automatically.

On top of that, I taught it to detect a long list of CI providers and pick a sensible level for each. Truecolor is used in: GitHub Actions, Gitea, and CircleCI. Basic 16-colour output is applied in GitLab, Travis, AppVeyor, Buildkite, Drone, Codeship, Azure Pipelines, TeamCity, AWS CodeBuild, Bitbucket Pipelines, Vercel, Netlify, Semaphore, Cirrus, Heroku, and Woodpecker.

The result is cached after the first check, so calling getLevel() over and over costs nothing.

More modifiers

I’ve rounded out the SGR set with blink, doubleUnderline, framed, encircled, overline, superscript, and subscript. I also export a bare reset colorizer, for the odd case where you’re writing raw bytes to stdout and need to cancel styling manually.

Utilities you probably already wanted

1import { strip, ansiRegex } from 'barva';
2
3strip(red.bold`hi ${blue`there`}!`); // => "hi there!"
4"…".replace(ansiRegex(), '');        // use the pattern directly

Type-safe chaining

In 1.2, TypeScript couldn’t follow chains like red.bold.underline, you either got @ts-expect-error or had to cast. I’ve made BarvaColorizer recursively typed, so chains, plus the new .rgb()/.hex()/.ansi256() methods, are fully type-checked. It exports the helper types too (BarvaColorizer, ColorLevel, ModifierName, ForegroundName, BackgroundName).

Under the hood

A few changes I think are worth mentioning:

  • Symbol brand: barva now identifies colorizers by a module-private Symbol, not a duck-typed _codes property, so plain objects with that field can’t be mistaken for a colorizer.
  • Stable ordering: chained codes always render in a deterministic order (modifiers first, then colours), regardless of how you chained them.
  • 100 tests, 99.6% line coverage with an enforced 95% threshold. I added a yarn test:coverage script that produces HTML, LCOV, and JSON summaries.
  • Every devDependency bumped to latest (ESLint 10, @types/node 25, globals 17, esbuild 0.28, tsup 8.5, Jest 30.3, ts-jest 29.4.9, and friends).

Bundle size

The new features cost a few bytes, but barva is still firmly in lightweight-library territory:

BuildMinifiedGzipped
ESM8.1 KB3.4 KB
CJS9.3 KB3.7 KB

Tree-shaking still works, so if you only import red and bold, the rest won’t ship.

On the roadmap

One thing I’ve deferred to a future release: a browser entry point that emits console.log’s %c formatters instead of ANSI codes, so barva can drop into isomorphic code without a second library. I’m tracking it in TODO.md in the repo. Feel free to open an issue on GitHub if you want this feature, or better yet, create a Pull Request 😎

Getting it

1yarn add barva
2# or
3npm install barva
4# or
5pnpm add barva

For the source, changelog, or to submit an issue you can go to: github. com/magikMaker/barva . I’ve created a few more open source npm packages, see: magikmaker.dev .