mrkeyoor.com_
Thu 06 Aug 02:41 UTC
npmCLI & Toolingupdated 06 Aug 2026

cli-table3

cli-table3 draws unicode box tables in a terminal. You create a table with an options object, push rows into it, and call toString() to get the finished block of text. The instance is literally an Array subclass, so push, splice, and length all work on the rows. Three layouts come free: horizontal tables with a header row, vertical tables that render key-value objects, and cross tables with both a top header and a left header. On top of that it does column widths, truncation with an ellipsis, word wrapping, horizontal and vertical alignment, per-cell padding, colSpan and rowSpan, fully customisable border characters, and colour styling for the header and border. It is the maintained continuation of the original cli-table and cli-table2, both abandoned, and it keeps their API so old code keeps working.

Verdict

Still the pragmatic choice for boxed terminal tables in a CommonJS Node CLI, especially when you need column and row spans. Read the source or the typings rather than the GitHub README for colour behaviour, because the published release is nearly two years behind what the README describes.

API stability5/5The constructor options and push-then-toString flow have been unchanged for years and remain compatible with the original cli-table from 2010, so upgrades have never required code changes.
Docs3/5The README covers the common cases and links to basic-usage and advanced-usage files generated from the test suite, which is a nice touch. But there is no API reference beyond the typings, and the colour section on the default branch documents an ansis-based implementation that is not in the published version.
Maintenance3/5The repository saw commits in April 2026 and carries only 13 open issues, but the last npm release, 0.6.5, is from May 2024, so fixes and the newer colour work sit unreleased on the default branch.
Ecosystem4/5Around 32.9M weekly downloads, most of it transitive: firebase-tools and @nestjs/cli both depend on 0.6.5. It has no plugin surface, but its cli-table compatibility means old examples and Stack Overflow answers still apply.

Use it if

  • Your CLI prints tabular output and you want borders, alignment, and column widths without writing padding logic that breaks on the first wide character
  • Your cells contain ANSI colour codes: width calculations run through string-width and strip the escape sequences, so coloured text does not blow out the column alignment
  • You need cells that span columns or rows, which is the main feature cli-table3 added over the original and which most other table packages still do not support
  • You are migrating from cli-table or cli-table2 and want a maintained drop-in: the API is deliberately unchanged, so the swap is a rename in package.json
Skip it if

Setup reality

npm install cli-table3, then const Table = require('cli-table3'). No config file, one real dependency, and the bundled index.d.ts uses export =, so TypeScript needs esModuleInterop or an import-equals statement. Three defaults surprise people. The header style defaults to red and the border style to grey, so output arrives coloured until you set style: {head: [], border: []}, which matters if you pipe it into a file or a log aggregator. colWidths counts the padding, so a width of 10 with the default padding leaves 8 columns for content. And anything longer than its column is truncated with an ellipsis rather than wrapped, unless you turn wordWrap on. Colour styling depends on the optional @colors/colors package; if it is absent the styles are swallowed silently.

Patterns

Header row plus data rowsbasic-table

const Table = require('cli-table3');

const table = new Table({
  head: ['Package', 'Version', 'Weekly'],
});

table.push(
  ['cli-table3', '0.6.5', '32.9M'],
  ['enquirer', '2.4.1', '33.4M']
);

console.log(table.toString());

The table instance extends Array, so push, concat, and length behave as expected. Column widths are measured from the content unless you set colWidths.

Key-value table from objectsvertical-table

const Table = require('cli-table3');

const table = new Table();

table.push(
  { 'Node.js': process.version },
  { Platform: process.platform },
  { CWD: process.cwd() }
);

console.log(table.toString());

Pushing single-key objects with no head option gives a two-column layout with the keys as a left header. Good for summary or doctor commands.

Both a top and a left headercross-table

const Table = require('cli-table3');

const table = new Table({ head: ['', 'passed', 'failed'] });

table.push(
  { unit: ['128', '0'] },
  { integration: ['41', '3'] }
);

console.log(table.toString());

The first entry of head must be an empty string to leave room for the left header column, and each row value is an array covering the remaining columns.

Fix column widths and truncate overflowfixed-widths

const Table = require('cli-table3');

const table = new Table({
  head: ['id', 'message'],
  colWidths: [8, 40],
  truncate: '...',
});

table.push(['a1b2c3d4', 'a commit subject line that runs well past forty characters']);

console.log(table.toString());

colWidths includes the left and right padding, so 40 gives 38 characters of content with the defaults. Overflow is cut and marked with the truncate string, which defaults to a single ellipsis character.

Wrap long text instead of cutting itword-wrap

const Table = require('cli-table3');

const table = new Table({
  head: ['file', 'error'],
  colWidths: [20, 45],
  wordWrap: true,
  wrapOnWordBoundary: true,
});

table.push(['src/index.js', longErrorMessage]);

console.log(table.toString());

wordWrap does nothing without a fixed width for that column. Set wrapOnWordBoundary to false when the content is a hash or a URL that should be broken anywhere rather than kept whole.

Align columns and rowsalignment

const Table = require('cli-table3');

const table = new Table({
  head: ['item', 'qty', 'total'],
  colWidths: [20, 8, 12],
  colAligns: ['left', 'right', 'right'],
});

table.push(['Widget', '3', '$45.00']);
console.log(table.toString());

colAligns handles horizontal alignment per column; rowAligns and the per-cell vAlign option handle vertical position when a row is taller than one line because of wrapping.

Cells that span columns or rowsspanning-cells

const Table = require('cli-table3');

const table = new Table();

table.push(
  [{ colSpan: 3, content: 'Build summary', hAlign: 'center' }],
  [{ rowSpan: 2, content: 'web', vAlign: 'center' }, 'bundle', '412 KB'],
  ['tests', '128 passed']
);

console.log(table.toString());

A cell object replaces the plain string. Rows covered by a rowSpan must omit the cells that are being spanned, otherwise the column count no longer lines up and the layout is wrong.

Change or remove the border characterscustom-borders

const Table = require('cli-table3');

const table = new Table({
  chars: {
    top: '', 'top-mid': '', 'top-left': '', 'top-right': '',
    bottom: '', 'bottom-mid': '', 'bottom-left': '', 'bottom-right': '',
    left: '', 'left-mid': '', mid: '', 'mid-mid': '',
    right: '', 'right-mid': '', middle: ' ',
  },
  style: { 'padding-left': 0, 'padding-right': 0 },
});

table.push(['name', 'value'], ['retries', '3']);
console.log(table.toString());

Emptying every character except middle produces plain aligned columns with no borders, which pipes into grep and awk far better than box drawing. Empty decoration lines are skipped rather than printed blank.

Turn off the default coloursplain-output

const Table = require('cli-table3');

const table = new Table({
  head: ['branch', 'ahead'],
  style: { head: [], border: [] },
});

table.push(['main', '0']);
console.log(table.toString());

Header text defaults to red and borders to grey, so redirecting output to a file leaves escape codes in it. Clearing both arrays is the reliable way to get plain text; do it whenever process.stdout.isTTY is false.

Style one cell differentlyper-cell-style

const Table = require('cli-table3');

const table = new Table({ head: ['check', 'status'] });

table.push(
  ['lint', { content: 'pass', hAlign: 'center' }],
  ['types', {
    content: 'fail',
    hAlign: 'center',
    style: { 'padding-left': 3, 'padding-right': 3 },
  }]
);

console.log(table.toString());

Per-cell style covers padding and border characters. For coloured content the simpler route is to wrap the string yourself with any ANSI helper before pushing it; the width maths already ignores escape codes.

Drop the separator lines between rowscompact-layout

const Table = require('cli-table3');

const table = new Table({
  head: ['id', 'name'],
  style: { compact: true },
});

rows.forEach(r => table.push([r.id, r.name]));
console.log(table.toString());

compact keeps the outer box and the line under the header but removes the divider between every data row, which matters once you are printing more than about ten rows on one screen.

Find out why a table renders wrongdebug-layout

const Table = require('cli-table3');

const table = new Table({ head: ['a', 'b'], debug: 1 });
table.push(['one', 'two']);

console.log(table.toString());
table.messages.forEach(m => console.log(m));
Table.reset();

The messages array only exists when debug is set, and it is populated by toString(), so read it afterwards. Call the static Table.reset() between tables or the messages from the previous one carry over.

Alternatives

PackageRegistryPick it when
tablenpmYou want a maintained ESM-friendly renderer with config validation and streaming table output.
console-table-printernpmYou are printing arrays of objects and want columns, colours, and sorting inferred for you.
columnifynpmYou want plain aligned columns with no borders, in the style of ps or docker output.