Skip to content

Table

pkg/table is a cursor-driven table of records inside a pane: a pinned header, sortable columns, fixed / content-sized / flex widths, a filter with column-scoped and regex terms, multi-select, row activity, and remote paging where scrolling is the pagination.

┌ Cities ────────────────────────────────────────┐
│/                                               │
│─────────────────────────────────────────────── │
│City ▲               │ Region     │  Population │
│─────────────────────────────────────────────── │
│Lima                 │ Americas   │       10.0M │
│London               │ Europe     │        8.9M │
│Madrid               │ Europe     │        3.3M │
│Nairobi              │ Africa     │        4.4M │
│Tokyo                │ Asia       │       13.9M │
│                                                │
└ 5 / 5 ────────────────────────────────── 1 / 5 ┘

Usage

opts := t.Table()
opts.Title = "Cities"
opts.Filterable = true
opts.Columns = []table.Column{
    {Title: "City", Flex: 1, Sortable: true},
    {Title: "Region", Width: 10, Sortable: true},
    {Title: "Population", Width: 11, Align: lipgloss.Right, Sortable: true, Less: popLess},
}
opts.Rows = []table.Row{{"London", "Europe", "8.9M"}, {"Tokyo", "Asia", "13.9M"}}
s.table = table.New(opts)

if s.table.IsActivate(msg) {
    if row, ok := s.table.Selected(); ok {
        return s, screen.Push(newCityDetail(row[0], s.t))
    }
}

Options

Field Type Default Meaning
Title string "Table" Border title.
Columns []table.Column required Column layout (below).
Rows []table.Row Initial rows; Row is []string, one cell per column.
Filterable bool false Filter row inside the pane; / focuses it.
Filter filter.Options theme Styles the embedded filter.
FilterMode table.FilterMode FilterLocal FilterRemote: report filters as QueryChangedMsg instead of applying them. Requires Filterable.
SortMode table.SortMode SortLocal SortRemote: report sorts instead of applying them.
SortDebounce time.Duration 400ms Quiet period before a remote sort commits. Negative commits immediately.
Anchored bool false Hold a span of anchored rows grown with AppendRows / PrependRows.
MaxItems int 5000 Resident cap for an anchored span.
Placeholder string "·" Cell text for rows a window doesn't hold.
HeaderStyle lipgloss.Style bold Header row.
SelectedStyle lipgloss.Style bold accent on subtle bg Cursor row.
CellStyle lipgloss.Style none Non-selected rows.
Borders table.Borders │ and ─ Interior separators (below).
HScrollbar bool true (theme) Horizontal scroll for wide tables.
Markable, MarkStyle false Multi-select. Requires keyed rows.
ActivityColumn string "" Column (by title prefix) whose cell shows the activity spinner.
BusyWhen func(KeyedRow) (string, bool) nil Which rows are working.
Revision func(KeyedRow) string nil A value that moves when work on the row finishes.
Activity activity.Options theme Spinner styling (cell-safe).
SpinnerStyle, LoadingLabel theme Loading spinner.
Keys table.Keys DefaultKeys() Key bindings.
pane pass-throughs theme See Components.

Columns

Field Meaning
Title Header text. Also the name key:value filter terms and ActivityColumn match against.
Width Visible cell width. ANSI escapes don't count.
Align lipgloss.Left (default), lipgloss.Right, lipgloss.Center.
Sortable Opt the column into sorting.
Less Custom comparator for the column (numbers, dates, "8.3M"). Default is case-insensitive on the ANSI-stripped text.
Flex Weight for a share of leftover width; Width becomes a minimum.
MaxWidth Cap on the effective width; surplus goes to other flex columns.
Hidden Keep the column in the data (filterable, in Selected, in messages) without drawing it.
Width Flex Sizing
> 0 0 fixed
0 0 content-auto: widest of title and cells, floor 4
any > 0 grows by weight into leftover space

When the table is narrower than the base widths, flex columns don't grow and the overflow goes to horizontal scroll. Widths recompute on row, column and size changes.

Borders

Borders{Vertical, HeaderRule} are pre-styled glyph strings. Vertical replaces the column gap with " <glyph> "; HeaderRule repeats its glyph under the header. Style them with ansi.CellColor (foreground-only) so the selected row's background passes through. Empty strings disable them.

Colored cells

Color cell text with ansi.CellColor(n, text), not lipgloss.Render: a full \x1b[0m reset inside a cell breaks the selected row's background. ansi.Hyperlink(url, label) makes a cell open a URL on shift/cmd-click even when truncated. See pkg/ansi.

Keys

Key Binding field Action
↑/k, ↓/j Up, Down move one row
g, G Top, Bottom first / last row
ctrl+u, ctrl+d HalfUp, HalfDown half a page
/ Filter focus the filter
tab (in the filter) complete a key:value term
[, ] SortPrev, SortNext step the sort column among sortable columns
s SortDir toggle sort direction
shift+←, shift+→ ColPrev, ColNext snap horizontal scroll to the previous / next column
←/h, →/l, 0, $ Pane.* horizontal scroll
x, X, A, D Mark, MarkRange, MarkAll, ClearMarks multi-select
enter activate (via IsActivate)

Mouse: click a row to select, double-click to activate, click a header to sort by it, wheel to move, click the ✓ gutter to toggle a mark, shift+click for a range.

Filter syntax

The filter splits on whitespace into AND-ed terms:

Term Matches
europe any cell containing "europe" (case-insensitive)
region:eu the column whose title starts with "region"
~^new any cell matching the regex (case-insensitive)
region:~^euro a regex scoped to one column

An ambiguous or unknown key is treated as literal text, as is a regex that doesn't compile, so the filter never refuses input. While typing a key:value term, the filter's bottom-left slot lists that column's matching values and tab completes to their longest common prefix. The grammar lives in pkg/query.

Data and selection

Method Does
SetRows([]Row) Replace rows; cursor kept by index.
SetKeyedRows([]KeyedRow) Replace rows with stable keys; the cursor follows its key. Use for polled data.
Rows(), KeyedRows(), Visible() All rows, keyed rows, post-filter/sort rows.
Selected() (Row, bool) The cursor row. false when empty, or on a placeholder under SetWindow.
SelectedIndex(), SelectedKey() Index into the unfiltered rows; the row's key.
Cursor(), SetCursor(n) Cursor within the visible rows.
Columns(), SetColumns(cols) Column layout.
SortColumn(), SortDescending(), SetSort(col, desc) Sort state (-1 = none).
Value(), SetValue(s) Filter text.
SetDistinct(col, values) Completion candidates for a column (for remote filters).
SetTitle, Title() Border title.
SetHeaderStyle, SetSelectedStyle, SetCellStyle Restyle rows in place.

KeyedRow is {Key string; Cells []string; Data any}.

Remote data

Method Does
SetWindow(rows, offset, total, answered) Hold rows [offset, offset+len) of total (-1 if unknown); everything else draws as placeholder.
Window() (offset, count, total) The resident window.
Query() QueryChangedMsg The query a source should be answering now (for the first fetch).
SetFailed(answer) The fetch for this query failed.
Stale(), Failed(), Answered(), Committed() Remote state.
CommittedSort(), StagedSort(), SetStagedSort() Debounced remote sort state.
AppendRows, PrependRows, Edges(), SetMore, Reanchor() Anchored spans.

table.Answer{Raw, Sort, Desc} names the query a set of rows answers. Prefer pkg/remote, which does all of this for you.

Messages

Message When
table.ActivatedMsg{Row, Cells, Token} double-click on a row (prefer IsActivate)
table.RowFocusedMsg{Row, Cells, Columns, Empty} the cursor lands on a different row
table.ViewportChangedMsg{FirstVisible, LastVisible, TotalRows} the visible slice changed: the signal to fetch
table.QueryChangedMsg{Raw, Terms, Sort, SortColumn, Desc} (remote modes) a filter was committed or a sort settled

Filters are reported on commit (enter, esc or blur), never per keystroke.

Multi-select and activity

Same surface as List: Selection(), SelectionLabel(), Marks(), SetMarks, ToggleMark, MarkRange, ToggleMarkAll, ClearMarks, MarkCount; and Dispatch, DispatchEach, Done, BeginRead, ApplyRead, ReadsFailing. Marking needs SetKeyedRows and is inert under SetWindow.

State

State() / Restore(st) carry rows (a window, a span or the whole set), cursor, scroll, filter, sort (committed and staged), marks, activity, the answered query, failure, completion values and loading.

See examples/components/table, examples/patterns/remote, examples/patterns/multiselect and examples/components/metrics.