tui

package
v0.9.6 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 28, 2026 License: BSD-3-Clause-LBNL Imports: 6 Imported by: 0

Documentation

Overview

Package tui provides reusable terminal UI components built on top of Bubbletea v2 and Bubbles v2. All components use pointer receivers so they can be stored as the Field interface in a Screen without extra copying.

Index

Constants

View Source
const (
	GlyphCursor = "❯"
	GlyphCheck  = "✓"
	GlyphCross  = "✗"
	GlyphBullet = "•"
)

Glyph constants used across all field renders.

Variables

View Source
var (
	ColorFocus   = lipgloss.Color("63")  // indigo  – focused borders / cursors
	ColorAccent  = lipgloss.Color("215") // amber   – titles / highlights
	ColorMuted   = lipgloss.Color("241") // grey    – descriptions / footer
	ColorError   = lipgloss.Color("196") // red     – validation errors
	ColorSuccess = lipgloss.Color("46")  // green   – success marks
	ColorText    = lipgloss.Color("252") // off-white – body text
	ColorBright  = lipgloss.Color("255") // white   – selected / important text
	ColorDim     = lipgloss.Color("240") // dark grey – unfocused options
)

Palette — all colours used across GDG TUIs in one place.

View Source
var (
	TitleStyle   = lipgloss.NewStyle().Bold(true).Foreground(ColorBright)
	DescStyle    = lipgloss.NewStyle().Foreground(ColorMuted)
	ErrorStyle   = lipgloss.NewStyle().Foreground(ColorError)
	FocusStyle   = lipgloss.NewStyle().Foreground(ColorFocus)
	BlurStyle    = lipgloss.NewStyle().Foreground(ColorDim)
	AccentStyle  = lipgloss.NewStyle().Foreground(ColorAccent)
	SuccessStyle = lipgloss.NewStyle().Foreground(ColorSuccess)
)

Shared text styles.

View Source
var DefaultKeys = Keys{
	Up:       key.NewBinding(key.WithKeys("up", "k"), key.WithHelp("↑/k", "up")),
	Down:     key.NewBinding(key.WithKeys("down", "j"), key.WithHelp("↓/j", "down")),
	Tab:      key.NewBinding(key.WithKeys("tab"), key.WithHelp("tab", "next field")),
	ShiftTab: key.NewBinding(key.WithKeys("shift+tab"), key.WithHelp("shift+tab", "prev field")),
	Enter:    key.NewBinding(key.WithKeys("enter"), key.WithHelp("enter", "confirm")),
	Space:    key.NewBinding(key.WithKeys(" ", "space"), key.WithHelp("space", "toggle")),
	Esc:      key.NewBinding(key.WithKeys("esc"), key.WithHelp("esc", "back")),
	Quit:     key.NewBinding(key.WithKeys("ctrl+c"), key.WithHelp("ctrl+c", "quit")),
}

DefaultKeys is the standard key map shared by all GDG screens.

Functions

func RunConfirm

func RunConfirm(title, description string) bool

RunConfirm runs a minimal full-screen program asking a single yes/no question. Returns true when the user selects Yes, false for No, Esc, or Ctrl+C. Intended for simple interactive confirmations outside of a larger TUI wizard.

Types

type ConfirmField

type ConfirmField struct {
	// contains filtered or unexported fields
}

ConfirmField is a yes/no two-option select bound to a *bool. ↑/← or y sets Yes; ↓/→ or n sets No.

func NewConfirmField

func NewConfirmField(title, desc string, ptr *bool) *ConfirmField

NewConfirmField creates a ConfirmField bound to ptr. The initial selection reflects *ptr: true → Yes (cursor 0), false → No (cursor 1).

func (*ConfirmField) Blur

func (f *ConfirmField) Blur()

func (*ConfirmField) Focus

func (f *ConfirmField) Focus() tea.Cmd

func (*ConfirmField) Focusable

func (f *ConfirmField) Focusable() bool

func (*ConfirmField) Update

func (f *ConfirmField) Update(msg tea.Msg) (Field, tea.Cmd)

func (*ConfirmField) Validate

func (f *ConfirmField) Validate() error

func (*ConfirmField) Value

func (f *ConfirmField) Value() bool

Value returns the current boolean selection.

func (*ConfirmField) View

func (f *ConfirmField) View(focused bool, width int) string

type Field

type Field interface {
	// Update handles keyboard / mouse input when this field is focused.
	// Returns the (possibly mutated) field and any follow-up command.
	Update(msg tea.Msg) (Field, tea.Cmd)

	// View renders the field to a string.  focused controls cursor / highlight
	// visibility.  width is the available horizontal space in terminal columns.
	View(focused bool, width int) string

	// Focus activates the field (shows cursor, starts blink animation, etc.)
	// and returns any initialisation command required.
	Focus() tea.Cmd

	// Blur deactivates the field (hides cursor, dims colours, etc.).
	Blur()

	// Focusable returns false for purely decorative / read-only fields (e.g.
	// NoteField) that the Screen should skip when cycling focus.
	Focusable() bool

	// Validate returns a non-nil error when the field's current value is
	// invalid.  Called by the Screen before advancing focus or submitting.
	Validate() error
}

Field is the interface implemented by every interactive element in a Screen.

All concrete types (*TextField, *SelectField, etc.) use pointer receivers so they satisfy the interface while being mutated in-place inside a Screen's field slice – no extra copying overhead per frame.

type Keys

type Keys struct {
	Up       key.Binding
	Down     key.Binding
	Tab      key.Binding
	ShiftTab key.Binding
	Enter    key.Binding
	Space    key.Binding
	Esc      key.Binding
	Quit     key.Binding
}

Keys holds all key bindings used by GDG TUI screens. It implements help.KeyMap so it can be passed directly to help.Model.View().

func (Keys) FullHelp

func (k Keys) FullHelp() [][]key.Binding

FullHelp implements help.KeyMap – shown when the user expands the footer.

func (Keys) ShortHelp

func (k Keys) ShortHelp() []key.Binding

ShortHelp implements help.KeyMap – shown in the compact one-line footer.

type MultiSelectField

type MultiSelectField struct {
	// contains filtered or unexported fields
}

MultiSelectField renders a checklist of options.

Key bindings (writable mode):

↑ / k   — move cursor up
↓ / j   — move cursor down
space   — toggle the focused item
enter   — confirm selection and advance / submit the screen

In read-only mode the cursor still moves but Space is ignored. Use WithReadOnly() to create a display-only list (e.g. showing which files will be affected before the user confirms).

Selected values are synced live to *ptr (slice of option Values).

func NewMultiSelectField

func NewMultiSelectField(title, desc string, opts []Option, ptr *[]string) *MultiSelectField

NewMultiSelectField creates a writable MultiSelectField bound to ptr. Options whose Value appears in *ptr are pre-checked.

func (*MultiSelectField) Blur

func (f *MultiSelectField) Blur()

func (*MultiSelectField) Focus

func (f *MultiSelectField) Focus() tea.Cmd

func (*MultiSelectField) Focusable

func (f *MultiSelectField) Focusable() bool

func (*MultiSelectField) Update

func (f *MultiSelectField) Update(msg tea.Msg) (Field, tea.Cmd)

func (*MultiSelectField) Validate

func (f *MultiSelectField) Validate() error

func (*MultiSelectField) View

func (f *MultiSelectField) View(focused bool, width int) string

func (*MultiSelectField) WithItemSelected

func (f *MultiSelectField) WithItemSelected(value string, sel bool) *MultiSelectField

WithItemSelected pre-selects or deselects a single option by value.

func (*MultiSelectField) WithReadOnly

func (f *MultiSelectField) WithReadOnly() *MultiSelectField

WithReadOnly returns a copy of the field configured for display only. The cursor can still move for readability, but Space does not toggle items. Focusable() still returns true so the screen cycles through it normally.

func (*MultiSelectField) WithSelected

func (f *MultiSelectField) WithSelected(values []string) *MultiSelectField

WithSelected pre-selects the options whose values are in the provided slice, replacing any prior selection. Useful for setting defaults after construction.

func (*MultiSelectField) WithValidate

func (f *MultiSelectField) WithValidate(fn func([]string) error) *MultiSelectField

WithValidate attaches a validation function (e.g. "at least one required"). Validation is skipped in read-only mode.

type NoteField

type NoteField struct {
	// contains filtered or unexported fields
}

NoteField is a read-only informational panel. It is not focusable; the Screen skips it when cycling focus. Pressing Enter on a screen whose only fields are NoteFields submits the screen immediately (the user just needs to acknowledge the information and move on).

func NewNoteField

func NewNoteField(title, body string) *NoteField

NewNoteField creates an informational display field.

func (*NoteField) Blur

func (f *NoteField) Blur()

func (*NoteField) Focus

func (f *NoteField) Focus() tea.Cmd

func (*NoteField) Focusable

func (f *NoteField) Focusable() bool

func (*NoteField) Update

func (f *NoteField) Update(_ tea.Msg) (Field, tea.Cmd)

func (*NoteField) Validate

func (f *NoteField) Validate() error

func (*NoteField) View

func (f *NoteField) View(_ bool, width int) string

type Option

type Option struct {
	Label string
	Value string
}

Option is a single choice shown in SelectField or MultiSelectField.

func NewOption

func NewOption(label, value string) Option

NewOption is a convenience constructor.

type Screen

type Screen struct {
	Submitted bool
	Cancelled bool
	ErrMsg    string // screen-level error (e.g. from multi-field validation)
	// contains filtered or unexported fields
}

Screen manages a list of Fields for one wizard step.

Responsibilities:

  • Tab / Shift-Tab cycle focus through focusable fields (wrapping).
  • Enter validates the current field and advances focus, or submits the screen when the last focusable field is confirmed.
  • Esc sets Cancelled = true (the parent model decides what "back" means).
  • Ctrl+C is intentionally NOT handled here; the parent model intercepts it.

After Update returns, callers check:

screen.Submitted — all fields valid, phase may advance
screen.Cancelled — user pressed Esc, phase should go back

Field values are bound to external pointers at construction time, so the parent can read results directly from its state struct without extracting them from the Screen.

func NewScreen

func NewScreen(width int, fields ...Field) Screen

NewScreen creates a Screen with the given width and fields.

The first focusable field is identified immediately so that key events are routed correctly even before Init() is called. This matters because bubbletea v2's Init() returns only a tea.Cmd — model mutations inside Init() are discarded — so we cannot rely on Init() to set the focus index.

func (Screen) Init

func (s Screen) Init() (Screen, tea.Cmd)

Init fires the Focus() command on the already-focused field so that textinput components start with their cursor blinking immediately. The focus *index* is set in NewScreen rather than here because bubbletea v2 discards model-level mutations made inside Init().

func (Screen) SetWidth

func (s Screen) SetWidth(w int) Screen

SetWidth updates the available horizontal space (called on WindowSizeMsg).

func (Screen) Update

func (s Screen) Update(msg tea.Msg) (Screen, tea.Cmd)

Update handles input for the active screen.

func (Screen) View

func (s Screen) View() string

View renders all fields stacked vertically.

type SelectField

type SelectField struct {
	// contains filtered or unexported fields
}

SelectField renders a single-choice list. ↑/↓ (or k/j) move the cursor; the selected value is synced live to *ptr.

func NewSelectField

func NewSelectField(title, desc string, opts []Option, ptr *string) *SelectField

NewSelectField creates a SelectField bound to ptr. The option whose Value matches *ptr is pre-selected; if *ptr is empty the first option is selected and *ptr is updated accordingly.

func (*SelectField) Blur

func (f *SelectField) Blur()

func (*SelectField) Focus

func (f *SelectField) Focus() tea.Cmd

func (*SelectField) Focusable

func (f *SelectField) Focusable() bool

func (*SelectField) Update

func (f *SelectField) Update(msg tea.Msg) (Field, tea.Cmd)

func (*SelectField) Validate

func (f *SelectField) Validate() error

func (*SelectField) Value

func (f *SelectField) Value() string

Value returns the currently highlighted option's value.

func (*SelectField) View

func (f *SelectField) View(focused bool, width int) string

type TextField

type TextField struct {
	// contains filtered or unexported fields
}

TextField wraps a bubbles textinput.Model, binding its live value to an external *string pointer so callers can read the result without an extra step after the screen is submitted.

func NewTextField

func NewTextField(title, desc string, ptr *string) *TextField

NewTextField creates a TextField with title, description, and a bound pointer. If ptr already holds a non-empty string the input is pre-filled.

func (*TextField) Blur

func (f *TextField) Blur()

func (*TextField) Focus

func (f *TextField) Focus() tea.Cmd

func (*TextField) Focusable

func (f *TextField) Focusable() bool

func (*TextField) SetError

func (f *TextField) SetError(msg string)

SetError displays an inline error message (used by Screen on validation failure).

func (*TextField) Update

func (f *TextField) Update(msg tea.Msg) (Field, tea.Cmd)

func (*TextField) Validate

func (f *TextField) Validate() error

func (*TextField) Value

func (f *TextField) Value() string

Value returns the current text in the input.

func (*TextField) View

func (f *TextField) View(focused bool, width int) string

func (*TextField) WithMask

func (f *TextField) WithMask() *TextField

WithMask enables password masking (dots instead of characters).

func (*TextField) WithPlaceholder

func (f *TextField) WithPlaceholder(p string) *TextField

WithPlaceholder sets greyed-out placeholder text shown when the field is empty.

func (*TextField) WithValidate

func (f *TextField) WithValidate(fn func(string) error) *TextField

WithValidate attaches a validation function called on Enter / submit.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL