4 min read
Bjørn Wikkeling
Share:

Barva 1.3.0 — truecolor, level detection, and proper types

Barva 1.3.0 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.

Barva — the tiny tagged-template terminal colour library — just shipped 1.3.0. It’s still zero-dependency and tree-shakable, but it now does a lot more out of the box.

Truecolor and the 256 palette

Up until 1.2 you had the 16 basic ANSI colours and their bright variants. As of 1.3, you get 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`);

Crucially, if the terminal doesn’t advertise truecolor support, the sequence is automatically downgraded 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: 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 there’s now a proper colour-level concept:

1import { getLevel, setLevel } from 'barva';
2
3getLevel();  // 0 | 1 | 2 | 3
4setLevel(3); // force truecolor output
5setLevel(undefined); // re-run environment detection

Detection is thorough: NO_COLOR, FORCE_COLOR, TERM, COLORTERM, WT_SESSION, VS Code’s terminal, and a long list of CI providers (GitHub Actions / Gitea / CircleCI at truecolor; GitLab, Travis, AppVeyor, Buildkite, Drone, Codeship, Azure Pipelines, TeamCity, AWS CodeBuild, Bitbucket Pipelines, Vercel, Netlify, Semaphore, Cirrus, Heroku, Woodpecker at basic). Results are memoised, so repeated checks are free.

More modifiers

blink, doubleUnderline, framed, encircled, overline, superscript, and subscript round out the SGR set. A bare reset colorizer is exported too, 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. BarvaColorizer is now recursively typed, so chains, plus the new .rgb()/.hex()/.ansi256() methods, are fully type-checked. The helper types (BarvaColorizer, ColorLevel, ModifierName, ForegroundName, BackgroundName) are all exported.

Under the hood

A few changes worth noting:

  • Symbol brand: colorizers are now identified 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. The yarn test:coverage script 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 some 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 still 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. Tracked in TODO.md in the repo.

Getting it

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

Source, changelog, and issues: https://github.com/magikMaker/barva .