Column spec, alignment & width

The {lcr} in \begin{tabular}{lcr} is one of the few places in LaTeX where you write a program rather than text. Beyond the number of columns and their alignment, you can instruct it to insert a declaration before and after every cell, to replace the space between columns with something else, or to repeat a group of columns. And the vocabulary of this column specification did not come from one design: it accumulated by donation. \newcolumntype began life in a separate package of David Carlisle’s, newarray.sty, and the array manual records that “David kindly agreed on the inclusion.” Starting from l, c, r and p{width}, this page follows the language through m{width} and b{width}, >{…} and <{…}, @{…} and !{…}, *{n}{…}, w and W, and finally the X column of tabularx that makes a table hit an exact width.

Which column specifiers need which package

Plain LaTeX gives you only l, c, r, p{width}, |, @{…} and *{n}{…}. Everything else — m{width}, b{width}, >{…}, <{…}, !{…}, w and W, and \newcolumntype — comes from the array package (TeX Live 2024 ships v2.5g of 16 October 2023, by Frank Mittelbach). The X column comes from tabularx (v2.11c of 8 July 2023, by David Carlisle), which itself requires array. Because booktabs and tabularx load array internally it is often already active, but writing \usepackage{array} explicitly is the safe move.

Which one is running in your document can be settled in one step: deliberately write a column letter that does not exist. Plain LaTeX answers ! LaTeX Error: Illegal character in array arg., whereas with array loaded the complaint comes from the package instead: ! Package array Error: Illegal pream-token. So if you wrote m{width} and were told “Illegal character”, the message is really telling you that you forgot to load array.

Paragraph columns p, m, b — and the fixed-width w and W

There are three wrapping paragraph columns, and they differ only in the vertical position of the cell. In the words of the array manual, p{width} is equivalent to \parbox[t]{width} and is top-aligned; m{width} places its content at the vertical centre of the row; and b{width} “coincides with \parbox[b]”, so it is bottom-aligned. The difference bites when a row mixes a tall cell — a p column wrapped to several lines, or a large image — with single-line cells. With the default p, a short neighbour clings to the top of the row and does not sit level with the middle of the tall cell; m{width} lines them up naturally.

When you want a fixed width but no wrapping, array offers w{align}{width} and W{align}{width}. Both work essentially like \makebox[width][align]{cell}: they behave like l, c or r while pinning the column to a fixed width. The two differ in how they fail — w silently overprints when the content is wider than the width, while W emits an overfull-box warning in the same situation. Note, though, that when W overflows it always protrudes to the right, so the apparent alignment changes. If the requirement is “fix the width but tell me when it overflows”, W is the right answer.

SpecifierVertical position / widthWraps?Provided by
p{wd}Top; equivalent to \parbox[t]{wd}YesStandard
m{wd}Centred vertically on the rowYesarray
b{wd}Bottom; coincides with \parbox[b]{wd}Yesarray
w{l}{wd}Like \makebox[wd][l]; silently overprints if too wideNoarray
W{l}{wd}Like w but warns on overflow; overflow goes rightNoarray
XA p column whose width is computed to hit a targetYestabularx

Two small defaults are worth remembering when using paragraph columns. First, inside p, m and b the paragraph indentation \parindent is 0; to bring it back, write something like >{\setlength{\parindent}{1em}}p{5cm}. Second, array adds a length called \extrarowheight. Given a positive value, it adds to the height of every row while leaving the depth unchanged — the most direct prescription for \hline rules that appear to touch the tops of capitals. The array manual notes that it set 1pt of it for the very table printed in the documentation.

Inserting around every cell — >{…} and <{…}

The centrepiece of the array package is >{decl} and <{decl}. >{decl} inserts decl immediately before the content of each cell of that column, <{decl} immediately after. Write >{\bfseries}l and every cell of a left-aligned column comes out bold without you typing anything per cell. >{\itshape}c gives centred italics, >{\color{red}}r right-aligned red. For paragraph columns you can also put alignment declarations there: >{\raggedright}p{4cm} makes a column that “wraps but does not justify”.

There is a serious trap, though. \raggedright, \raggedleft and \centering all redefine the row terminator \\ internally. So if a column carrying one of them in >{…} is the table’s last column, \\ stops ending rows, the next row’s & overflows, and you get ! Extra alignment tab has been changed to \cr.. The cause is in the column spec but the error points at a body line, which is why nobody spots it the first time. The cure is to append array’s \arraybackslash at the end of the declaration: >{\raggedright\arraybackslash}p{4cm} restores the row terminator. It does no harm on a column that is not last, so the safer habit is to write it always.

The origin of \arraybackslash has a twist. It was introduced by tabularx, precisely to repair this clash with \raggedright. Today it is array.sty itself that carries \def\arraybackslash{\let\\\tabularnewline}, and tabularx keeps only a guarded fallback that defines it if nobody else has. In other words, the tool was born in one package and now lives in another.

latex
\usepackage{array}
% ...
% bold labels; a ragged-right wrapping column; a whole column in math mode
\begin{tabular}{ >{\bfseries}l >{\raggedright\arraybackslash}p{5cm} >{$}c<{$} }
  \hline
  Symbol & Meaning & \multicolumn{1}{c}{Value} \\
  \hline
  c & Speed of light in vacuum, a defining constant. & 2.998\times10^{8} \\
  g & Standard gravitational acceleration near the surface. & 9.807 \\
  \hline
\end{tabular}

The >{$}c<{$} used in the third column is the original motivation for > and <. Each cell gets a $ before and after, so every cell in that column is set as inline mathematics and you never type $…$ again in a numeric or symbol-heavy column. Do the same inside an array environment, whose cells are already in math mode, and the two $ cancel, putting just that column into text mode. The header row needs care, though: writing Value plainly gets it set as mathematics, coming out as loosely spaced italics. As in the example above, \multicolumn{1}{c}{Value} returns that one cell to text mode.

Remember also the constraint that only a declaration may go in >{…} and <{…}. Forms that take no following argument, like \bfseries or \itshape, are fine, but you must not end with a command that takes an argument. >{\textbf} starts reading the cell’s content as its argument and fails in the most awkward way possible: no error, and nothing comes out bold. The array manual states it plainly — >{\textbf} “would not make the whole column bold nor would it make the first character bold.” For bold, use the declaration form \bfseries.

At the column boundary — @{…} and !{…}

Between two columns LaTeX inserts, by default, \tabcolsep of space — 6pt on each side, so 12pt in total. Put @{decl} at that position in the spec and, in the array manual’s wording, it “suppresses inter-column space and inserts decl. instead”: the gap disappears and whatever you wrote takes its place. There are two main uses. First, trimming the table’s outer padding: @{} (an empty declaration) at both ends of the spec removes the \tabcolsep to the left of the first column and the right of the last, so the table sits flush with the text’s left edge — a familiar idiom in booktabs tables. Second, inserting a separator: split numbers into an integer-part column and a fractional-part column and put @{.} between them, and the periods line up at a fixed position, giving decimal alignment.

The counterpart to @{…} is !{decl}. It goes where a vertical rule | could go and inserts an arbitrary decl in place of the rule. The decisive difference is that !{…} does not remove the normal inter-column space. The array manual’s own example makes it concrete: c!{\hspace{1cm}}c gives a gap that is the existing space plus 1cm, whereas c@{\hspace{1cm}}c gives exactly 1cm. When you want, say, a dashed line between columns but with the padding kept (putting a dashed-rule command from arydshln inside it), !{…} is the right choice. Incidentally, loading array also changes how | behaves: the column gap widens by the thickness of the rule, whereas in standard LaTeX the rule sits inside the existing space and the width does not change.

Repeating and naming columns — *{n}{…} and \newcolumntype

When many identically specified columns run in a row, *{count}{spec} folds them up. In the array manual’s definition it is equivalent to count copies of spec, so six centred columns become *{6}{c} instead of cccccc. The repeated unit may hold several specifiers: *{3}{|c}| expands to |c|c|c|. Its real value shows when the repeated spec is long and contains >{…} or @{…}*{4}{>{$}c<{$}} builds four centred math-mode columns in one stroke.

If the same combination recurs throughout a document, giving it a name with \newcolumntype{letter}{…} reads far better. The array manual itself offers \newcolumntype{C}{>{$}c<{$}} as its example, after which {C C C} is all you write. \newcolumntype accepts the same optional argument as \newcommand, so you can also build a column type that takes the width as a parameter. When the definitions pile up and you lose track, write \showcols: it prints the list of all currently active \newcolumntype definitions into the log.

Making a table fit a target width — the X column of tabularx

Every specifier so far let you decide the width. The opposite case — “make the whole table exactly \textwidth” — is what the X column of tabularx is for. Pass the target width first, as in \begin{tabularx}{\textwidth}{l X r}, and the columns marked X are widened or narrowed by calculation to make up the difference. What makes X approachable is how plain the mechanism is: as the manual states, the X specification is converted to p{some value} once the correct width has been calculated. In the source, the default is the single line \newcommand{\tabularxcolumn}[1]{p{#1}}. X is therefore nothing more than “a p column whose width is filled in later”, which is exactly why the same decorations work on it — >{\raggedright\arraybackslash}X behaves as you would expect. Want the content vertically centred? Redefine it: \renewcommand{\tabularxcolumn}[1]{m{#1}}.

document.tex
\usepackage{tabularx}
% ...
% two X columns, the second twice as wide as the first
\begin{tabularx}{\textwidth}{@{}
    l
    >{\hsize=.667\hsize\linewidth=\hsize}X
    >{\hsize=1.333\hsize\linewidth=\hsize}X @{}}
  \hline
  Key & Short note & Longer note \\
  \hline
  alpha & fits in the narrow column & twice as wide, so prose wraps less often \\
  \hline
\end{tabularx}

By default every X column ends up the same width, but writing >{\hsize=…\hsize\linewidth=\hsize}X as above changes their weights. The manual imposes two rules, though: keep the sum of the X-column widths unchanged (0.667 and 1.333 above add up to 2, that is, still two standard X columns), and do not use a \multicolumn entry that crosses any X column, because the width calculation does not account for one. If something misbehaves, declare \tracingtabularx and the log will print the column widths tried on each pass.

Two symptoms specific to X are worth knowing. If the non-X columns already total more than the target width, tabularx refuses to give the X columns a negative width: it emits Package tabularx Warning: X Columns too narrow (table too wide) and falls back to setting them 1em wide, so the table comes out wider than you asked for. Second, the body of a tabularx is really read as the argument to a command and the table is typeset repeatedly while the widths are searched for, which is why a \verb inside it raises LaTeX Warning: \verb may be unreliable inside tabularx. Comparing tabularx with tabulary and tabularray, and handling tables that will not fit the page at all, belongs to “Wide tables and auto-width”.

A worked example, and the modern answer for aligning numbers

Finally, one column spec that packs in the tools above. Column 1 is a bold label column (>{\bfseries}l); column 2 a flush-left wrapping paragraph column (>{\raggedright\arraybackslash}p{5cm}); columns 3 and 4 are centred math-mode columns built together with *{2}{…}; and the outer padding is trimmed with @{} at both ends. Packing several mechanisms into one line is the strength of the column language — and also what makes it hard to read, which is why naming a frequent combination with \newcolumntype is the practical move.

document.tex
\usepackage{array}
% ...
\begin{tabular}{@{} >{\bfseries}l >{\raggedright\arraybackslash}p{5cm} *{2}{c} @{}}
  \hline
  Symbol & Meaning & Value & Unit \\
  \hline
  $c$ & Speed of light in vacuum, a defining constant. & $2.998\times10^{8}$ & m/s \\
  $g$ & Standard gravity, an approximation near the surface. & $9.807$ & m/s$^2$ \\
  \hline
\end{tabular}

One thing this column language is better not used for: aligning numbers on the decimal point. Reach for the S column of the siunitx package rather than the @{.} sketched earlier. The S column parses each number and aligns integer part, decimal point, fractional part, exponent and even uncertainties correctly, and tidies digit grouping and significant figures along the way. Incidentally, the array manual prints its own decimal-alignment code and then attaches the warning that “the code is bad”, pointing readers to a much better version in David Carlisle’s dcolumn.sty — a package manual reviewing its own example that honestly is a rare sight.