TermMosaic v1.0.0

v1.0.0 is the first release that makes a stability promise: the public API freezes here and Semantic Versioning applies in earnest — with one documented exception: widgets/widgettest is excluded from that promise, because it must evolve with the framework. Read the limitations before you rely on anything here.

Widgets widgets/form

Tabs

A horizontal strip of labels, one of which is the active pane.

A horizontal strip of labels, one of which is the active pane.

Package widgets/form. API reference: pkg.go.dev/widgets/form.

Tabs is a single row of tab labels with one selected, scrollable when the tabs do not all fit.

Rendered output

Tabs rendered by cmd/capture through widgettest.Capture: colour at 3 widths (40, 80, 120 columns), and the exact plain-text cell grid at the widest of them.

MinSize() returns 1 × 1 cells. That is the whole widget including its own chrome, and it is the widget's assertion about itself — below it, Draw clips rather than blanks, and what to do about that is the application's decision.

40 columns × 4 rows

 Logs [Metrics] Traces  Config          
                                        
                                        
                                        

80 columns × 4 rows

 Logs [Metrics] Traces  Config                                                  
                                                                                
                                                                                
                                                                                

120 columns × 4 rows

 Logs [Metrics] Traces  Config                                                                                          
                                                                                                                        
                                                                                                                        
                                                                                                                        

Plain text — the exact cell grid, trimmed per line. This is the honest form: Braille and block-element glyphs can take a different advance width in a web font than a terminal gives them, which breaks the alignment they depend on.

# Tabs — A horizontal strip of labels, one of which is the active pane.
# package: widgets/form
# constructor: form.NewTabs(r buffer.Rect, labels []string) *form.Tabs
# 120 columns x 4 rows, rendered through widgettest.Capture

 Logs [Metrics] Traces  Config



What you are looking at. This is the cell grid the renderer produced, in a web page. It is not a screenshot of a terminal, and it cannot show interaction, resize or animation — a capture is one settled frame after N frames of settling. Braille and Block Elements can take a different advance width in your browser's font than in a terminal, so if a gauge or a sparkline above looks wrong, the plain-text form below it is the exact output and the colour one is the decoration. The colour frames are at 40, 80, 120 columns; the plain-text frame is at 120. More on this.

Package context

Package form provides the input widgets: TextInput, TextArea, Select, Checkbox, Radio, Toggle, Tabs, Button and KeyHint.

Four rules, each of which is a consequence of something decided elsewhere, and each of which is enforced by this package’s tests rather than by convention:

  • A widget’s space is Bounds(), never buf.Size() (ADR 0007 §1 rule 1).
  • Every widget repaints its entire Bounds() before drawing content, because the renderer diffs and never clears (ADR 0007 §1 rule 3).
  • Draw is total for every rect, including 0×0, 1×1 and anything below MinSize, and it clips rather than blanking (ADR 0007 §4).
  • Nothing derived from the size or from the text is built inside Draw. Wrap, Truncate and span construction allocate, so each widget caches the result against the rect it was computed for and rebuilds when the rect, the text or the styles change (ADR 0007 §3, ADR 0008 §4).

Constructing it

form.NewTabs(r buffer.Rect, labels []string) *form.Tabs

NewTabs takes the labels. The labels are the tab strip; the panes behind them are yours — Tabs selects, it does not contain.

  • TabStyle — buffer.Style — an unselected label.
  • SelectedStyle — buffer.Style — the active label. The active tab also carries a distinct glyph, so it survives NO_COLOR.
  • Ascii — bool — ASCII tab glyphs.

Key contract

Keys are consumed only while focused. Moving with an arrow SELECTS, as in every tab bar: there is no separate commit step, because a tab bar has no unselected state to commit from.

Keys Effect
left / right KeyLeft / KeyRight select the previous / next tab
up / down KeyUp / KeyDown do the same, so a tab bar works in a vertical form layout too
home / end KeyHome / KeyEnd select the first / last tab
page up/down KeyPageUp / KeyPageDown select as many tabs as fit
activate KeyEnter or KeySpace fires OnSelect for the selected tab
wheel MouseWheelUp / MouseWheelDown scroll without selecting, and only over the pointer being inside Bounds (ADR 0010)

Accessibility

The selected tab is BRACKETED — “[Name]” — and every other tab is space-padded — " Name “. The bracket is a shape, so the selection is readable in a monochrome terminal, under NO_COLOR, and by a reader who sees no difference between two colours an application chose. This is the requirement from ADR 0008’s accessibility rule: a selected tab must be distinguishable without colour.

Keeping the unselected mark a space rather than nothing is deliberate. It means every tab is exactly its label plus two cells, so selecting a different tab cannot reflow the row, and the brackets are unambiguously the selection indicator rather than decoration.

When not to use it

Do not use it to hold the panes. Tabs is the strip and the selection. The content behind each tab is a separate widget you show and hide. If you find yourself wanting a Tabs that owns its panes, that is Split plus a Tabs above it, and it is two widgets on purpose.

Do not use it for a value. It is navigation, not a control that produces data. If the user is choosing one of N values, that is Select or Radio.

Do not expect the tab strip and the pane to resize together. Tabs takes its own rect. Give it the strip row; give the pane the rest. That is a solved constraint list, not a nesting relationship.

Do not use it when there are more tabs than fit and the important one is off-screen. It scrolls, and which direction it scrolls in and where it lands on a state change is worth checking against your own labels.

Instead: Split plus a Tabs above it for content panes; Select for a value.