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

Select

A single-choice control that expands into a scrollable list.

A single-choice control that expands into a scrollable list.

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

Select is a closed list of options with one highlighted, scrollable when the options do not all fit.

Rendered output

Select 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 × 9 rows

  alpine
  bookworm
  cachyos
  debian
>  endeavouros                          
  fedora                                
  gentoo                                
  nixos                                 
  void                                  

80 columns × 9 rows

  alpine
  bookworm
  cachyos
  debian
>  endeavouros                                                                  
  fedora                                                                        
  gentoo                                                                        
  nixos                                                                         
  void                                                                          

120 columns × 9 rows

  alpine
  bookworm
  cachyos
  debian
>  endeavouros                                                                                                          
  fedora                                                                                                                
  gentoo                                                                                                                
  nixos                                                                                                                 
  void                                                                                                                  

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.

# Select — A single-choice control that expands into a scrollable list.
# package: widgets/form
# constructor: form.NewSelect(r buffer.Rect, labels []string) *form.Select
# 120 columns x 9 rows, rendered through widgettest.Capture

  alpine
  bookworm
  cachyos
  debian
>  endeavouros
  fedora
  gentoo
  nixos
  void

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.

About this capture. A Select draws its options below the field rather than over the page, so the capture shows ten of the ten labels with the fifth highlighted.

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.NewSelect(r buffer.Rect, labels []string) *form.Select

NewSelect takes the labels up front as a []string; the list is closed. widgets/form/optionlist.go is the unexported shared helper behind Select, Tabs and KeyHint — it is not a widget and has no page.

  • Marker — string — the glyph drawn beside the highlighted option.
  • SelectedStyle — buffer.Style — the highlighted option row. Unset means reverse video, so the selection survives NO_COLOR.
  • Ascii — bool — force ASCII markers.

Key contract

Keys are consumed only while focused. The wheel is consumed whether or not the widget is focused, because pointing at a list and scrolling it does not require taking focus away from whatever is being typed in.

Keys Effect
up / down KeyUp / KeyDown highlight one option
left / right KeyLeft / KeyRight highlight one option, so the widget works in a horizontal form layout too
home / end KeyHome / KeyEnd highlight the first / last option
page up/down KeyPageUp / KeyPageDown highlight one screenful
activate KeyEnter or KeySpace accepts the highlighted option, which fires OnSelect without changing the highlight
wheel MouseWheelUp / MouseWheelDown scroll without moving the highlight, and only over the pointer being inside Bounds (ADR 0010)

A closed list never changes what it contains, so there is no type-ahead and no Remove: a Select whose options change is a different widget.

Accessibility

The highlighted option is marked by SelectDefaultMarker in its own marker column; every other option carries a space in that column. That is the non-colour signal, and it is deliberately a separate column rather than part of the label: a marker that moved with the text would shift every row’s label one cell, which is exactly the misalignment a column exists to prevent.

When not to use it

Do not use it for a set the user can add to. The label list is given at construction. An editable combobox would need text input, a filtered option list and a caret, and none of that is here — that is TextInput plus your own filtering.

Do not use it when the option labels must wrap. Options are truncated to the field width. A long option is marked as truncated rather than shown in full, so pick short labels.

Do not use it in a Block whose width is driven by the label rather than by the layout. MinSize() reports 1×1, which means the widget is not asserting a minimum — not that it renders well at one cell. The options are what need room, and the framework will not compute that for you.

Do not confuse it with Tabs or Radio. All three are one-of-N. Select draws the options below the field; Radio draws them all at once; Tabs is for switching between panes of content. Choosing wrong here is a layout decision you will regret.

Instead: Radio to show every option at once, Tabs to switch panes, TextInput plus filtering for an open set.