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

Radio

A single-choice group of labels, navigated with the arrow keys.

A single-choice group of labels, navigated with the arrow keys.

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

Radio is a one-of-N group of options: exactly one is chosen, and the choice is visible without colour.

Rendered output

Radio 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 6 × 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 × 6 rows

  ( ) horizontal
  ( ) vertical
> (o) both                              
                                        
                                        
                                        

80 columns × 6 rows

  ( ) horizontal
  ( ) vertical
> (o) both                                                                      
                                                                                
                                                                                
                                                                                

120 columns × 6 rows

  ( ) horizontal
  ( ) vertical
> (o) both                                                                                                              
                                                                                                                        
                                                                                                                        
                                                                                                                        

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.

# Radio — A single-choice group of labels, navigated with the arrow keys.
# package: widgets/form
# constructor: form.NewRadio(r buffer.Rect, labels []string) *form.Radio
# 120 columns x 6 rows, rendered through widgettest.Capture

  ( ) horizontal
  ( ) vertical
> (o) both



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

NewRadio takes the labels; the selection is a field.

  • FocusStyle — buffer.Style — the whole group while focused. Unset means reverse video on the selected label.
  • OptionStyle — buffer.Style — unselected labels.
  • Ascii — bool — (/) rather than the Unicode radio set.

Key contract

Keys are consumed only while focused. Moving with an arrow CHOOSES, which is how every radio group behaves: there is no separate “commit” step, because a radio group has no unselected state to commit from.

Keys Effect
up / down KeyUp / KeyDown choose the previous / next option and fire
left / right KeyLeft / KeyRight, so the group works in a horizontal form
home / end KeyHome / KeyEnd choose the first / last option
page up/down KeyPageUp / KeyPageDown choose one screenful away
activate KeyEnter or KeySpace re-fires OnSelect for the chosen option, which is what a user pressing it to confirm expects
wheel MouseWheelUp / MouseWheelDown scroll without choosing, and only over the pointer being inside Bounds (ADR 0010)

Accessibility

Two independent non-colour signals, which is what a radio group needs and one checkbox is not enough of:

  • the chosen option’s marker is “(o)” and every other option’s is “( )”, so the choice is a SHAPE difference;
  • the focused option carries a “>” in its own focus column, so focus is not signalled by the same thing that signals selection.

More on accessibility

The focus gutter column exists on every row, because that is what keeps the labels aligned, but it is not a focus mark unless the group actually has focus. Until v0.5.1 an unfocused group painted a reverse-video stripe down its left edge, which read as a focus indication on rows that did not have it.

When not to use it

Do not use it when the options do not all fit. MinSize() is 6×1, which is one row — the widget will show what fits and you are responsible for noticing. If the option count is dynamic and unbounded, Select scrolls; Radio does not.

Do not use it for a set the user adds to at runtime without rebuilding the widget, and do not expect the selection to survive that rebuild unless you hold it.

Do not use it to mean “pick several”. A radio group is exactly one. If the user needs any combination, use Checkbox per option.

Do not use it when vertical space is the scarce resource and the options are long. Every option is a row. One column of options that could have been a one-row Select is usually the wrong trade.

Instead: Select when the options overflow, Checkbox when several may be chosen.