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.
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.
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
- You need this in a browser or an ESM-only project. It is CommonJS with a Node-flavoured API, and it pins string-width to the v4 line, which is the last CommonJS major of that package
- You expect the GitHub README to match what you installed. The README on the default branch documents colour styling through ansis with hex truecolor helpers, but the published 0.6.5 from May 2024 still uses the optional @colors/colors dependency and knows nothing about hex codes
- You want live or streaming output. toString() renders the whole table in one pass, so a progress table means clearing and reprinting the entire block yourself
- You want colours to be reliable. Styling is applied inside a try/catch around an optional dependency, so installing with --omit=optional, or passing a style name it does not recognise, silently produces plain text with no warning
- You want data-driven tables with sorting, filtering, or markdown output. console-table-printer and table cover those; this package only draws what you push into it
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
| Package | Registry | Pick it when |
|---|---|---|
| table | npm | You want a maintained ESM-friendly renderer with config validation and streaming table output. |
| console-table-printer | npm | You are printing arrays of objects and want columns, colours, and sorting inferred for you. |
| columnify | npm | You want plain aligned columns with no borders, in the style of ps or docker output. |