Documentation
¶
Overview ¶
Package table provides a cursor-driven, optionally filterable tabular view inside a bordered pane. Each Row is a []string of cell text aligned against a fixed-width Column slice. The header pins to the top of the pane (it does not scroll out of view as the cursor moves), but it scrolls horizontally with the body so columns stay aligned with their titles when the user scrolls a wide table left/right.
Cells are rendered ANSI-aware via x/ansi.Cut, so colored content (foreground-only escapes such as pkg/ansi.CellColor) survives column truncation without leaking color into adjacent cells. There is no "+8 budget for the ANSI escape" caveat — column Width is the visible width of the cell and that's what truncation respects. Lipgloss styles are also fine inside cells (header/selected styling applies on top of any inner styling), but inner full-reset SGR sequences will still clobber the selected-row background; prefer foreground-only color escapes for status-style cells when you need the row highlight to pass through unbroken.
Filter syntax (when Filterable=true): the input is split on whitespace into AND-ed terms. A bare term matches any cell as a case-insensitive substring. A term shaped "key:value" scopes the match to the column whose Title case-insensitively starts with key (e.g. "region:europe"); an ambiguous or unknown key falls through as a literal bare term, which is also how to search for a literal colon. A term whose value starts with "~" is compiled as a case-insensitive Go regex (e.g. "~^new", "region:~^euro"); compile errors fall back to a literal substring including the tilde, so the parser never refuses input. While the user is mid-typing a "key:val" term the filter pane's bottom-left slot lists the column's distinct values matching val, and tab completes val to the longest common prefix of the remaining candidates — regex terms skip the hint since enumerating regex matches isn't useful.
Horizontal nav: ←/→ (or h/l) scroll by HScrollStep cells; 0/home jump to the leftmost edge; $/end jump to the rightmost edge; shift+←/shift+→ snap the viewport to the previous / next column boundary so a wide table can be stepped column-by-column instead of cell-by-cell.
Sort: set Column.Sortable to opt a column in. Keys are "[" / "]" to step the active sort column among Sortable columns and "s" to toggle direction; the active column gets a ▲ / ▼ marker after its title. Default comparator is case-insensitive on the ANSI-stripped cell text; override per-column with Column.Less for numeric, date, or unit-aware sort. SortColumn() / SortDescending() / SetSort(col, desc) carry sort state across SetTheme rebuilds the same way Cursor / Value do.
Index ¶
- type Borders
- type Column
- type KeyedRow
- type Model
- func (m Model) Columns() []Column
- func (m Model) Cursor() int
- func (m Model) Filtering() bool
- func (m Model) Help() []key.Binding
- func (m Model) Init() tea.Cmd
- func (m Model) Loading() bool
- func (m Model) Rows() []Row
- func (m Model) Selected() (Row, bool)
- func (m Model) SelectedIndex() (int, bool)
- func (m Model) SelectedKey() (string, bool)
- func (m *Model) SetActiveColor(c lipgloss.TerminalColor)
- func (m *Model) SetCellStyle(s lipgloss.Style)
- func (m *Model) SetColumns(cols []Column)
- func (m *Model) SetCursor(n int)
- func (m *Model) SetDimensions(w, h int)
- func (m *Model) SetFocused(b bool)
- func (m *Model) SetHeaderStyle(s lipgloss.Style)
- func (m *Model) SetInactiveColor(c lipgloss.TerminalColor)
- func (m *Model) SetKeyedRows(rows []KeyedRow)
- func (m *Model) SetLoading(b bool) tea.Cmd
- func (m *Model) SetLoadingLabel(s string)
- func (m *Model) SetRows(rows []Row)
- func (m *Model) SetSelectedStyle(s lipgloss.Style)
- func (m *Model) SetSort(col int, desc bool)
- func (m *Model) SetSpinnerStyle(s lipgloss.Style)
- func (m *Model) SetTitle(s string)
- func (m *Model) SetValue(s string)
- func (m Model) SortColumn() int
- func (m Model) SortDescending() bool
- func (m Model) Update(msg tea.Msg) (Model, tea.Cmd)
- func (m Model) Value() string
- func (m Model) View() string
- func (m Model) Visible() []Row
- type Options
- type Row
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Borders ¶
type Borders struct {
// Vertical, when non-empty, replaces the single-space inter-column
// separator with " <glyph> " (visible width 3). Typical values:
// "│" (light), "┃" (heavy), "╎" (dashed). Pre-style the glyph.
Vertical string
// HeaderRule, when non-empty, draws a horizontal rule between the
// header row and the first data row by repeating the field's first
// visible rune to the table's full visible width. Typical values:
// "─" (light), "═" (double), "·" (dotted). Pre-style the glyph; the
// SGR escapes are extracted and re-applied around the repeated rune.
HeaderRule string
}
Borders controls the two interior separators a table draws — the inter-column glyph and the horizontal rule below the header. Both fields are pre-styled glyph strings; pass them through pkg/ansi.CellColor (foreground-only) so the selected row's background passes through unbroken (rule 17). Set a field to "" to disable it.
type Column ¶
type Column struct {
Title string
Width int
// Align controls cell padding within the column. Use lipgloss.Left
// (default), lipgloss.Right (good for numeric columns), or
// lipgloss.Center.
Align lipgloss.Position
// Sortable allows the user to sort by this column with [/] (step
// active sort column) and s (toggle direction). Non-sortable columns
// are skipped during step. The active column gets a ▲/▼ marker
// rendered after its title.
Sortable bool
// Less compares two cell strings for this column. If nil, sortable
// columns use a case-insensitive comparison on the ANSI-stripped
// text — fine for plain string columns. Set Less for numeric, date,
// or unit-aware columns ("8.3M") that need custom parsing.
Less func(a, b string) bool
// Flex, when > 0, makes this column absorb a share of leftover
// horizontal space after every column's base width is accounted for.
// Multiple flex columns split the remainder proportionally
// (Flex=1 + Flex=2 → 1:2 split). The base width acts as a minimum:
// a flex column never shrinks below it, but it does grow when room
// is available. When the table is narrower than the sum of base
// widths, flex columns get no expansion (extra space goes to the
// pane's horizontal scroll, not column reflow).
Flex int
// MaxWidth, when > 0, caps the column's effective width — flex
// growth never pushes it above this value. When a flex column hits
// its cap, the surplus redistributes to the remaining uncapped
// flex columns by their weights (iteratively, so chains of caps
// are handled). When every flex column is capped, leftover space
// stays unused on the right edge of the row.
MaxWidth int
}
Column declares one column's title, width (in visible cells), and cell alignment. Width sizing modes:
- Width > 0, Flex == 0: fixed width.
- Width == 0, Flex == 0: content-auto — sized to the widest of title and any cell value (ANSI-aware, floor of 4).
- Flex > 0: column expands to absorb a share of leftover horizontal space, weighted by Flex. Width (or content-auto when Width==0) acts as a minimum; MaxWidth (when > 0) caps growth.
type KeyedRow ¶
KeyedRow pairs a stable identity Key with the row's cells. Pass through SetKeyedRows when the row source is polled so the cursor can re-bind to the same Key after a refresh — when the row at the cursor's Key reappears in the new set the cursor follows it; otherwise it falls back to the clamped previous index. Use SelectedKey to read the current row's identity. KeyedRow is a separate path (not a swap of the Row type) so existing SetRows callers see no change.
type Model ¶
type Model struct {
// contains filtered or unexported fields
}
Model is the table widget. Embed as a value; mutate via the setters.
func (Model) Selected ¶
Selected returns the currently highlighted row. ok is false when the visible set (post-filter) is empty.
func (Model) SelectedIndex ¶
SelectedIndex returns the highlighted row's index into the original (pre-filter) Rows() slice. Use this when callers maintain a parallel source slice and need to identify which source row is selected.
func (Model) SelectedKey ¶
SelectedKey returns the highlighted row's Key when the table was populated via SetKeyedRows. ok is false when no row is selected, or when the rows were set via SetRows (which carries no keys). Callers that drive a polled source should track the selection by Key, not by SelectedIndex, so re-fetches that reorder rows don't shift the selection out from under the user.
func (*Model) SetActiveColor ¶
func (m *Model) SetActiveColor(c lipgloss.TerminalColor)
SetActiveColor / SetInactiveColor update the body pane's border colors. Useful for theme swaps that don't rebuild the model.
func (*Model) SetCellStyle ¶
func (*Model) SetColumns ¶
SetColumns replaces the column layout. Cell text is preserved; effective widths recompute on the next refresh.
func (*Model) SetDimensions ¶
SetDimensions resizes the table in place. When filterable, the internal filter pane consumes 3 rows at the top and the body pane gets the rest.
func (*Model) SetFocused ¶
SetFocused sets the body pane's focus state (controls border color).
func (*Model) SetHeaderStyle ¶
SetHeaderStyle / SetSelectedStyle / SetCellStyle update row styling.
func (*Model) SetInactiveColor ¶
func (m *Model) SetInactiveColor(c lipgloss.TerminalColor)
func (*Model) SetKeyedRows ¶
SetKeyedRows replaces the row set with each row carrying a stable Key, then snaps the cursor to whichever row in the new set shares the previously-selected Key. When the previous Key has disappeared (row removed from the source), the cursor falls back to the clamped previous index so the user lands near where they were. The filter and sort state are preserved across the swap. This is the primitive pkg/poll uses to keep the user's place across periodic refreshes.
func (*Model) SetLoading ¶
SetLoading toggles the loading state. Returns the spinner's first Tick when entering — propagate it back so the spinner animates.
func (*Model) SetLoadingLabel ¶
SetLoadingLabel updates the text rendered next to the spinner.
func (*Model) SetRows ¶
SetRows replaces the row set, re-applies the current filter, redraws. Cursor is preserved by visible index — fine for static datasets, but callers polling a live source should prefer SetKeyedRows so the cursor rebinds to the same logical row even when neighbours come and go.
func (*Model) SetSelectedStyle ¶
func (*Model) SetSort ¶
SetSort sets the sort column and direction. col == -1 disables sort; otherwise col must reference a Sortable column. Use this on rebuild (theme swap) to carry SortColumn/SortDescending across the new model.
func (*Model) SetSpinnerStyle ¶
SetSpinnerStyle updates the lipgloss style applied to the spinner glyph.
func (Model) SortColumn ¶
SortColumn returns the active sort column index (-1 when no sort).
func (Model) SortDescending ¶
SortDescending reports the active sort direction.
func (Model) Update ¶
Update consumes cursor + filter keys; non-key messages flow to the body pane so spinner ticks reach the loading-state animation.
type Options ¶
type Options struct {
Width, Height int
// Title sits on the pane's top-left border slot. Defaults to "Table".
Title string
// Columns declares the column layout. Required.
Columns []Column
// Rows is the full row set. The table copies this slice so the caller
// can mutate their source independently.
Rows []Row
// Filterable embeds a filter.Model above the body pane (three rows).
// See the package doc for the full filter syntax — bare substring,
// "key:value" column scope, "~regex" prefix, and the distinct-value
// hint + tab completion that fires while typing a "key:" term.
Filterable bool
// Pane pass-throughs.
ActiveColor lipgloss.TerminalColor
InactiveColor lipgloss.TerminalColor
ActiveBorder lipgloss.Border
InactiveBorder lipgloss.Border
SlotBrackets pane.SlotBracketStyle
// HScrollbar reserves a row at the bottom of the body pane and lets
// ←/h and →/l scroll wide tables horizontally. theme.Table() enables
// this by default — disable when columns are guaranteed to fit.
HScrollbar bool
// HeaderStyle is applied to the header row (typically bold). Header
// cells are still padded/aligned by column before the style runs.
HeaderStyle lipgloss.Style
// SelectedStyle is applied to the highlighted row. theme.Table()
// uses bold + Accent fg + Subtle bg.
SelectedStyle lipgloss.Style
// CellStyle is applied to non-selected rows. Defaults to no style.
CellStyle lipgloss.Style
// SpinnerStyle styles the loading-state spinner glyph. Pass via
// theme.Table() for a sensible default.
SpinnerStyle lipgloss.Style
// LoadingLabel is rendered next to the spinner while loading.
LoadingLabel string
// Filter configures the embedded filter.Model. Ignored when
// Filterable=false. Theme.Table() pre-fills this from Theme.Filter().
Filter filter.Options
// Borders configures table-internal separators. Each field is emitted
// verbatim, so pre-style with pkg/ansi.CellColor (foreground-only) so
// the selected-row background passes through. Empty fields disable
// the corresponding separator. Theme.Table() sets sensible defaults.
Borders Borders
}
Options configures a new table. Theme.Table() returns this pre-styled — set Title/Columns/Rows/Filterable/Filter on the returned value.