Why roundel
roundel against chalk, one capability per row, every cell linked to the test, grade or source that proves it.
chalk styles a string, and does it well: roundel/chalk passes all 58 cases of chalk 6.0.0's
own suite, and roundel's colour-level rule agrees with chalk's across a differential sweep
except where this page says it does not. What chalk leaves to every package that uses it is the
decision around the colour — whether this output is going to a terminal, a CI log, a screen
reader or an agent, and what the colours mean. roundel makes that decision once, as a pure
function, and gives the colours names.
The table below is the whole comparison. Every mark links to its evidence: a test in this
repository for ours, and for chalk the source file of chalk 6.0.0 — the version compat-oracle
grades — or chalk's own test suite. scripts/capabilities-lock.test.ts fails the build when a
cited test no longer contains the title it is cited for.
✓ yes · ◐ partial, with what is missing · ✗ no · — does not apply. Every mark links to its evidence: our test or grade, or the incumbent’s source at the version compat-oracle grades.
| Capability | roundel | chalk |
|---|---|---|
| One output policy | ||
Five output modes from one ruleoutputMode() answers json, accessible, ci, pipe or tty from the runtime it is given, first match wins, so a spinner, a prompt and the help ask one question and get one answer. | roundel: yes | chalk: nodecides a colour level and nothing else; whether to redraw is left to each package |
NO_COLOR turns colour off, over FORCE_COLOR and --colorA user who sets NO_COLOR gets no colour, whatever else the environment or the command line asks for. | roundel: yes | chalk: noits colour detection never reads NO_COLOR |
FORCE_COLOR=0 is never overridden by a --color flagAn explicit "colour off" in the environment wins over --color=256 or --color=16m on the command line, so off never turns into on. | roundel: yes | chalk: yesan environment FORCE_COLOR replaces the flag's answer before any --color level is read |
--color, --color=256 and --no-color flagsThe flags chalk reads are read under the same spellings, from the argv the program hands the policy. | roundel: yes | chalk: yesread from process.argv at import |
--no-color beats an ambient FORCE_COLORA flag typed for this run outranks a FORCE_COLOR the machine exports, so --no-color under FORCE_COLOR=3 is no colour. | roundel: yes | chalk: noa set FORCE_COLOR replaces the flag's answer, so --no-color under FORCE_COLOR=3 is level 3 |
An agent on a terminal is not someone to promptinteractive() answers no under CLAUDECODE, CURSOR_AGENT and the other agent variables even with a terminal on stdin, so a prompt asks the one question that tells an agent from a person. | roundel: yes | chalk: does not applydecides a colour level, not whether a person can answer |
A screen-reader mode with no colourWith CLI_ACCESSIBLE=1 the level is 0 even on a truecolor terminal, because escape codes are noise to a screen reader; an explicit ask still colours it. | roundel: yes | chalk: noreads no accessibility switch |
--json output is never colouredUnder --json the level is 0 even when FORCE_COLOR asks for colour, so structured output never carries an escape. | roundel: yes | chalk: nohas no notion of structured output; the program sets chalk.level = 0 itself |
| The decision is a pure function of a runtimeThe policy reads only the environment, arguments and terminal flag it is handed, so a test or an agent harness asks the program's question and gets the program's answer. | roundel: yes | chalk: noreads process.env, process.argv and the file descriptors' TTY state once, at import |
| Colour levels | ||
| Truecolor, 256 and 16 colours, downsampledA hex colour is sent as truecolor at level 3 and as the nearest of 256 or 16 colours below it. | roundel: yes | chalk: yesthrough its vendored ansi-styles |
| A CI vendor's colour level, once colour is asked forGitHub Actions, Gitea Actions and CircleCI get truecolor and the other known vendors 16 colours, while a CI pipe nobody asked to colour stays plain. | roundel: yes | chalk: yesthe same vendor table |
| Tokens and themes | ||
Semantic tokens instead of colour namesA program writes error(), hint() and command(), and the theme decides what each looks like, so the colours mean something and change together. | roundel: yes | chalk: noevery colour is named at the call site |
A theme checked for WCAG contrastfly() refuses a hex token below 4.5:1 against the declared background, at every colour level, and names the token and its ratio. | roundel: yes | chalk: nodraws any colour it is given |
Themes through a validated plugin registryA plugin's tokens are validated at register(), a misspelt token is refused rather than dropped, and the contrast gate applies to them as to a hand-written theme. | roundel: yes | chalk: nohas no themes to register |
| Weight | ||
| No runtime dependenciesInstalling it adds one package and nothing else. | roundel: yes | chalk: yesansi-styles and supports-color are copied inside it |
| Compatibility | ||
Passes chalk's own test suiteroundel/chalk is graded by chalk 6.0.0's own tests, unedited, so changing the import keeps chalk's output. | roundel: yes58 / 58 of its own tests | chalk: yesits own suite, the control run |
One output policy
Five output modes from one rule
outputMode()answersjson,accessible,ci,pipeorttyfrom the runtime it is given, first match wins, so a spinner, a prompt and the help ask one question and get one answer.roundel- roundel: yes
NO_COLORturns colour off, overFORCE_COLORand--colorA user who sets
NO_COLORgets no colour, whatever else the environment or the command line asks for.roundel- roundel: yes
FORCE_COLOR=0is never overridden by a--colorflagAn explicit "colour off" in the environment wins over
--color=256or--color=16mon the command line, so off never turns into on.roundel- roundel: yes
--color,--color=256and--no-colorflagsThe flags chalk reads are read under the same spellings, from the argv the program hands the policy.
roundel- roundel: yes
--no-colorbeats an ambientFORCE_COLORA flag typed for this run outranks a
FORCE_COLORthe machine exports, so--no-colorunderFORCE_COLOR=3is no colour.roundel- roundel: yes
An agent on a terminal is not someone to prompt
interactive()answers no underCLAUDECODE,CURSOR_AGENTand the other agent variables even with a terminal on stdin, so a prompt asks the one question that tells an agent from a person.roundel- roundel: yes
A screen-reader mode with no colour
With
CLI_ACCESSIBLE=1the level is 0 even on a truecolor terminal, because escape codes are noise to a screen reader; an explicit ask still colours it.roundel- roundel: yes
--jsonoutput is never colouredUnder
--jsonthe level is 0 even whenFORCE_COLORasks for colour, so structured output never carries an escape.roundel- roundel: yes
The decision is a pure function of a runtime
The policy reads only the environment, arguments and terminal flag it is handed, so a test or an agent harness asks the program's question and gets the program's answer.
roundel- roundel: yes
Colour levels
Truecolor, 256 and 16 colours, downsampled
A hex colour is sent as truecolor at level 3 and as the nearest of 256 or 16 colours below it.
roundel- roundel: yes
A CI vendor's colour level, once colour is asked for
GitHub Actions, Gitea Actions and CircleCI get truecolor and the other known vendors 16 colours, while a CI pipe nobody asked to colour stays plain.
roundel- roundel: yes
Tokens and themes
Semantic tokens instead of colour names
A program writes
error(),hint()andcommand(), and the theme decides what each looks like, so the colours mean something and change together.roundel- roundel: yes
A theme checked for WCAG contrast
fly()refuses a hex token below 4.5:1 against the declared background, at every colour level, and names the token and its ratio.roundel- roundel: yes
Themes through a validated plugin registry
A plugin's tokens are validated at
register(), a misspelt token is refused rather than dropped, and the contrast gate applies to them as to a hand-written theme.roundel- roundel: yes
Weight
No runtime dependencies
Installing it adds one package and nothing else.
roundel- roundel: yes
Compatibility
Passes chalk's own test suite
roundel/chalkis graded by chalk 6.0.0's own tests, unedited, so changing the import keeps chalk's output.
Reading it
- Parity rows are here on purpose. chalk reads the
--colorflags, keeps an explicitFORCE_COLOR=0off, downsamples to 256 and 16 colours, knows the CI vendors and has no dependencies. A reader would otherwise have to go and check; the cells say they match. - chalk's cells link to chalk 6.0.0 on jsDelivr, not to the copy installed in this repository, which is 6.0.1: the lock only accepts an installed file at the graded version. A URL cell is not re-read by the lock, so each was checked against that file by hand; the compatibility row, whose source is chalk's vendored suite, is checked on every run.
What is not in the table
A row goes in only when every cell of it can be proved. These were left out:
npx roundel check. It validates a theme plugin before it ships; chalk has no plugins, so there is nothing to compare it with. It is on Theme plugins.- The per-façade level. Setting
chalk.levelonroundel/chalkchanges that façade and not the tokens. chalk also has separate instances (new Chalk({ level })), so the difference is in what the default instance is shared with, not a yes-or-no capability. - Terminal allow-list detection. chalk recognises kitty, ghostty, wezterm, iTerm, Apple
Terminal, TeamCity and the Windows build number; roundel deliberately does not, and reads only
TERM,COLORTERMand explicit instructions (Colour levels). That is a difference in approach, and the row would read as a loss or a win depending on the terminal. - Weight in bytes. The per-subpath figures are asserted by
weight.test.tsand published on Benchmarks.
Theme plugins
A plugin contributes tokens as #rrggbb colours under the family's one plugin shape; register() validates it, theme() merges it, later wins, and fly() holds it to the same contrast gate.
Compatibility
How roundel/chalk is graded — chalk 6.0.0's own test suite, unedited — the current grade, and the differences that remain.