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
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 survivesNO_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.
Related
RadioandTabs— the other two one-of-N shapes.- Forms
widgets/form/optionlist.goon GitHub — the unexported helper, for reading.- TermMosaic limitations that apply to every widget
- Source:
widgets/formon GitHub