Build Reliable groff Tables with tbl

You reach for tbl the moment spaces stop lining up in a troff document and you need an actual column, not a guess. This walks through a working roff source file for GNU tbl, with patterns for numeric columns, repeated formats and long cells. The examples match GNU tbl 1.23.0 from the groff-base package installed on this machine, and gtbl is the compatible alias.

Allow 15 minutes. You need tbl and groff; no elevated privileges, because the examples only read standard input and write standard output. They use semicolons as cell separators so you can paste them without worrying about preserving literal tab characters.

1. Confirm the tool and version

Check the installed binary before trusting syntax from a different groff release:

$ tbl --version
GNU tbl (groff) version 1.23.0

The command is a preprocessor, not a formatter in its own right. It copies ordinary input untouched and translates only the table regions between .TS and .TE into input troff understands. In normal documents, invoke it through groff -t rather than building a separate pipeline.

Checkpoint: gtbl --version should report the same version. On this installation the two manpage files are aliases, so there is no separate feature set to learn.

2. Write the smallest useful table

Save this as /tmp/tbl-demo.roff, or paste it straight into the command in the next step:

.TS
box centre tab(;);
Cb Cb
L L.
Command;Purpose
tbl;prepares tables
groff;formats documents
.TE

The line after .TS sets region options: box draws a border, centre centres the table, and tab(;) swaps the default tab separator for a semicolon. The format ends at the dot after L L. Cb Cb makes the first row centred and bold; L L makes the rest left-aligned.

Format the input as a terminal preview:

$ groff -t -Tutf8 /tmp/tbl-demo.roff
Command   Purpose
tbl       prepares tables
groff     formats documents

Terminal output is deliberately plain: some rules and spacing are device-specific. For a printable or PDF document, choose the appropriate groff output device instead.

3. Align numbers and change a format

Use N for numeric alignment. It lines entries up on the units position, usually the decimal separator. A comma or semicolon inside a cell is not special unless it is the configured separator, so keep the row delimiter unambiguous:

.TS
centre tab(;);
Cb Cb
L N.
Package;Size (MiB)
groff-base;12.5
groff;3
.T&
L N.
Total;15.5
.TE

.T& starts another table description inside the same region. It can change alignment for later rows, but it cannot add columns: the first format already fixed two, so the second is safe.

Checkpoint: Run groff -t -Tutf8 your-file.roff and check the numeric values line up. A value with several decimal separators aligns on the rightmost one. If the data is not numeric, use L or R instead of forcing N.

4. Keep long text inside a cell

Ordinary entries are measured as rigid text, so one long sentence can drag the whole table wider than the page. Put prose in a text block instead, opening with T{ at the end of the entry line and closing with T} at the start of a line:

.TS
box tab(;);
Lw(1.2i) Lw(3.5i).
Option;Meaning
center;T{
Centre the table in the current line length and leave the other
columns to use the remaining space.
T}
.TE

The w modifier sets a minimum column width in ens; a parenthesised roff measurement such as w(3.5i) is easier to review at a glance. A text block is formatted by troff itself, so it wraps over multiple output lines. It cannot be nested, and it must fit on one page.

If a table ends up wider than the available line length, shorten the text, set more suitable widths, or apply x to columns that may expand. Do not paper over a cramped layout with nowarn: that suppresses the formatter's diagnostics, it does not fix the table.

5. Make special rows explicit

An underscore-only row draws a horizontal rule, and an equals-only row draws a double rule on devices that support it. The corresponding cells must contain nothing but the rule marker:

.TS
box tab(;);
L R.
Item;Count
apples;12
oranges;8
_;_
Total;20
.TE

A lone underscore or equals sign carries that special meaning everywhere, so to display one literally, prefix it with the roff dummy character \&. The same trick makes an intentionally empty cell obvious in the source. Vertical spans use \^ in a later row and horizontal spans use the S classifier, but spanning has to stay rectangular: a first-column horizontal span or a first-row vertical span is an error.

6. Diagnose the common failures

Safety boundary: Tbl does not need root access. Avoid running it as root just to read a document, and review input from untrusted sources before feeding it into a larger roff build. A table can emit formatter requests of its own, so preprocessing is not a sandbox.

Done means