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:
barvanow identifies colorizers by a module-privateSymbol, not a duck-typed_codesproperty, 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:coveragescript that produces HTML, LCOV, and JSON summaries. - Every devDependency bumped to latest (ESLint 10,
@types/node25,globals17, 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:
| Build | Minified | Gzipped |
|---|---|---|
| ESM | 8.1 KB | 3.4 KB |
| CJS | 9.3 KB | 3.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 .
