Render a table¶
Renderer.Table renders a slice of structs or maps as an aligned text table — or,
when the renderer's format is csv/tsv/markdown/json/yaml, in that
machine-readable form instead. One call, every format.
From struct tags¶
Tag the fields you want as columns with table:"Header[,sortable]":
type Release struct {
Tag string `table:"Tag,sortable"`
Date string `table:"Date,sortable"`
Assets int `table:"Assets,sortable"`
Notes string `table:"-"` // excluded
}
r := output.New(output.WithWriter(os.Stdout), output.WithFormat(output.FormatText))
_ = r.Table(releases, output.WithSortBy("Date"), output.WithSortDescending())
Explicit columns (and maps)¶
[]map[string]any has no tags, so pass columns explicitly. WithColumns also lets
you set widths and per-cell formatters:
_ = r.Table(rows, output.WithColumns(
output.Column{Header: "Name", Field: "name", Sortable: true},
output.Column{Header: "Size", Field: "bytes", Formatter: humanize},
))
Options¶
| Option | Effect |
|---|---|
WithColumns(cols...) |
define columns explicitly (required for maps) |
WithSortBy(header) |
sort by a Sortable column (numeric-aware) |
WithSortDescending() |
reverse the sort |
WithNoHeader() |
omit the header row |
WithNoTruncation() |
do not truncate to terminal width (e.g. piping to a file) |
WithMaxWidth(n) |
override terminal-width detection |
Not every option applies to every format —
behaviour by format
has the matrix. In short: json and yaml ignore all of them, and csv/tsv
ignore WithNoHeader.
Sorting: what the key actually is¶
WithSortBy takes the column's Header, not the Go field name. For
Tag string `table:"Tag,sortable"` the two happen to coincide; for
Name string `table:"NAME,sortable"` the sort key is "NAME". A mismatch
returns unknown sort column: "...", and a column without sortable returns
column "..." is not sortable.
The sort runs on the rendered cell text, after any Formatter. Each pair is
compared with strconv.ParseFloat first and falls back to a string compare, so
a numeric column sorts numerically — until a formatter adds a unit:
// "100 B" sorts before "9 B": both fail ParseFloat, so this is a string sort.
output.Column{Header: "Size", Field: "Size", Sortable: true, Formatter: bytesWithUnit}
If you need both a numeric sort and a formatted display, sort on a raw column and format a separate one.
Machine-readable formats¶
The same call honours the renderer's format. With output.WithFormat(output.FormatCSV):
Markdown output is safe by construction: a literal | in a cell is
backslash-escaped and newlines fold to <br>, so a value can never break out of
its cell or open a new row.
Unicode¶
Column widths and truncation use display width, not byte length, via
charmbracelet/x/ansi. CJK, emoji and combining characters align correctly and a
truncated cell never splits a rune mid-sequence.
When a table will not render¶
Table returns an error rather than guessing:
| You passed | Error |
|---|---|
| a single struct, not a slice | rows must be a slice |
[]map[string]any without WithColumns |
Table requires a slice of structs or explicit WithColumns |
a struct with no table: tags |
no table tags found on struct |
an untyped nil |
cannot derive columns from nil; use WithColumns |
A Column.Field that names nothing is not an error — the cell is empty. Full
list, including the three inputs that panic, in
errors and failure modes.
What tables cannot do¶
No right alignment, no cell wrapping (long values are truncated, not folded), no borders, no footer row, and no dotted paths into nested structs. See what this does not do for the workarounds.