tuilib

module
v0.0.0-...-e04c4e5 Latest Latest
Warning

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

Go to latest
Published: May 6, 2026 License: MIT

README

tuilib

A component library for building Bubble Tea TUIs quickly and reliably. Each component is a small, well-documented pkg/ with an Options struct, a New(Options) constructor, and standard Bubble Tea Init/Update/View methods. A central theme package collapses the color palette into one struct so every component renders in the same palette without drift.

Quickstart

The fastest path to a working TUI is pkg/app + one screen.Screen. The shell handles breadcrumb + statusbar + theme cycling; your screen returns a layout.Node tree and local state.

package main

import (
    "fmt"
    "os"

    "github.com/charmbracelet/bubbles/key"
    "github.com/charmbracelet/bubbles/textinput"
    tea "github.com/charmbracelet/bubbletea"

    "github.com/jsdrews/tuilib/pkg/app"
    "github.com/jsdrews/tuilib/pkg/layout"
    "github.com/jsdrews/tuilib/pkg/list"
    "github.com/jsdrews/tuilib/pkg/screen"
    "github.com/jsdrews/tuilib/pkg/theme"
)

type cities struct {
    t    theme.Theme
    list list.Model
}

func (s *cities) Title() string            { return "Cities" }
func (s *cities) Init() tea.Cmd            { return textinput.Blink }
func (s *cities) OnEnter(any) tea.Cmd      { return nil }
func (s *cities) IsCapturingKeys() bool    { return s.list.Filtering() }
func (s *cities) Layout() layout.Node      { return layout.Sized(&s.list) }
func (s *cities) Help() []key.Binding {
    return []key.Binding{
        key.NewBinding(key.WithKeys("/"), key.WithHelp("/", "filter")),
        key.NewBinding(key.WithKeys("q"), key.WithHelp("q", "quit")),
    }
}

func (s *cities) Update(msg tea.Msg) (screen.Screen, tea.Cmd) {
    var cmd tea.Cmd
    s.list, cmd = s.list.Update(msg)
    return s, cmd
}

func (s *cities) SetTheme(t theme.Theme) {
    s.t = t
    cursor, value := s.list.Cursor(), s.list.Value()
    opts := t.List()
    opts.Title = "Cities"
    opts.Items = []string{"London", "Tokyo", "Madrid", "Lima"}
    opts.Filterable = true
    s.list = list.New(opts)
    if value != "" { s.list.SetValue(value) }
    s.list.SetCursor(cursor)
}

func main() {
    m := app.New(app.Options{
        Root:   &cities{},
        Themes: []theme.Theme{theme.Nord()},
    })
    if _, err := tea.NewProgram(m, tea.WithAltScreen()).Run(); err != nil {
        fmt.Println(err); os.Exit(1)
    }
}

No m.h-2 math, no breadcrumb/statusbar wiring, no resize handler — the app shell owns that. The screen just declares shape (via Layout()) and handles its own state in Update.

Components

Package What it does
pkg/app Standard shell — breadcrumb + body + statusbar, theme cycling, global-key routing, auto esc→pop
pkg/screen Screen interface + Stack with push/pop and result passing via OnEnter(result)
pkg/layout Declarative layout engine: VStack/HStack/ZStack + Fixed/Flex — no m.h-2 math
pkg/breadcrumb One-line header strip with click-or-keyboard crumbs
pkg/pane Bordered, titled, scrollable region with slot metadata around the border — the primitive every other component wraps. Truncates long lines to inner width and supports horizontal scroll (←→ / h / l) with an optional thin scrollbar. Built-in loading state replaces the body with a centered spinner via SetLoading(true).
pkg/statusbar Three-slot footer (left/middle/right) with info/error middle states
pkg/help Key-hint renderer (ShortView inline, FullView overlay)
pkg/filter Textinput in a pane; "/" to focus, enter commits, esc clears
pkg/list Cursor-driven, optionally filterable list inside a pane. SelectedIndex() returns the underlying source-slice index even when items are formatted display strings and a filter is active. SetKeyedItems([]KeyedItem{Key,Display}) + SelectedKey() snap the cursor to the same Key after a swap (the auto-refresh primitive — pair with pkg/poll). Vim-style nav: g/G top/bottom, ctrl+u/ctrl+d half-page, plus ↑↓ per row
pkg/table Cursor-driven, optionally filterable tabular view inside a pane. Column{Title, Width, Align, Sortable, Less, Flex, MaxWidth} declares the layout; rows are []string cells. Header pins to the top while scrolling horizontally with the body so columns stay aligned. ANSI-aware truncation via x/ansi.CutWidth is the visible cell width. Sizing: Width > 0 → fixed; Width == 0 → content-auto (max of title + any cell, ANSI stripped, floor 4); Flex > 0 → absorbs a share of leftover inner width by weight (Width is the min, MaxWidth is the cap; surplus from capped columns redistributes to uncapped flex columns). Recomputes on row/column/dimension changes, so flex columns reflow on resize. Same nav verbs as pkg/list (g/G, ctrl+u/d, ↑↓/j/k); filter matches across all cells (ANSI stripped before matching) and accepts space-separated AND-ed terms; a key:value term scopes the match to the column whose Title starts with key (e.g. region:europe pop:5); a ~ prefix on the value makes that term a case-insensitive regex (e.g. ~^new, region:~^euro). Mid-typing a key:val term shows the column's distinct matching values in the filter's bottom-left slot, and tab completes to the longest common prefix. SetKeyedRows([]KeyedRow{Key,Cells}) + SelectedKey() snap the cursor to the same Key after a swap (the auto-refresh primitive — pair with pkg/poll). Sortable columns expose [/] (step active sort column) + s (toggle direction); supply Column.Less for numeric or unit-aware sort. Options.Borders{Vertical, HeaderRule} configures the inter-column glyph and the horizontal rule below the header — both are pre-styled glyph strings (pkg/ansi.CellColor keeps the selected-row bg intact); theme.Table() ships with subdued / defaults
pkg/input Single-line text input in a pane; bare textbox without filter's commit/cancel keys
pkg/toggle Yes/no selector in a pane — left/right/space/y/n
pkg/confirm Modal yes/no dialog with title + message + confirm/cancel buttons; resolves via ConfirmedMsg / CancelledMsg so parent screens stay bubbletea-idiomatic. Designed for layout.ZStack(base, layout.Center(w, h, ...))
pkg/alert Modal acknowledgement dialog with title + message + single OK button; resolves via DismissedMsg. Use for "stop and acknowledge" feedback (errors, blocking notices); for passive feedback prefer the lighter app.Info / app.Error statusbar messages. Override ActiveColor with theme.ErrorBG for an error-tinted look
pkg/logview Streaming text viewer with /-search, n/N jump, g/G top/bottom, filter mode, current-line highlight, and a default MaxLines safety cap
pkg/tree Searchable, expand/collapse hierarchical viewer over any Node (Label + Children); /-search highlights inline and \ hides non-matching subtrees while keeping ancestors. Labels may contain lipgloss-styled ANSI (colored status icons, etc.) — the cursor's row highlight stays intact across colored segments
pkg/inspector Two-column label/value viewer for structured records (k8s manifests, REST responses, Prefect run details). Field{Label, Value, Children} composes; FromAny / FromMap convert json.Unmarshal output into Fields. Sibling labels auto-align per group, ▸/▾ expand nested objects/arrays, / searches labels and values, \ hides non-matching subtrees. SetFields preserves expansion state + cursor by row path across swaps — the auto-refresh primitive for inspector
pkg/poll Interval-driven tea.Cmd ticker for auto-refresh. Emits a typed RefreshMsg parents match in their Update to kick off a refetch; Pause/Resume/SetInterval/Refresh (now) / MarkRefreshed. Pair with the keyed-row APIs on pkg/list and pkg/table (or path-keyed SetFields on pkg/inspector) so cursor + expansion survive every swap
pkg/metrics Inline rendering primitives for list rows / table cells / inspector values: Badge(ok, warn, down) (status-count summary), Ratio(done, total) (severity-colored "N/M"), Bar(value, max, width) (fixed-width progress bar), Spark(values, width) (8-step block sparkline that resamples to fit). All return ANSI-safe foreground-only strings via pkg/ansi.CellColor so the selected-row background passes through. Rendering-only — caller owns history buffer for sparklines
pkg/form Vertical layout of input + toggle (+ Select) fields with tab cycling and a submit button
pkg/tab Tabbed container hosting multiple screen.Screen bodies behind a one-row strip. Each body keeps its own state across switches; shift+left/shift+right and 19 switch tabs (tab/shift+tab is left alone for inner pane focus cycling). Host screen forwards Update/OnEnter/IsCapturingKeys/SetTheme/Help to tabs
pkg/runner Hand the terminal to an interactive subprocess (vim, htop, less, ssh) and resume the TUI on exit. Clears the screen on handoff by default; RunWithNotice prints a transitional line for slow handoffs (kubectl exec, ssh); RunWith(Options{...}) for full control
pkg/theme Single palette struct + per-component Options builders. app.New resolves the initial theme from Options.ThemeEnvVar + the user config file automatically (set SkipConfig=true to opt out)
pkg/config YAML user-config at ~/.config/tuilib/config.yaml. Pure data + I/O; opt-in (library never writes). Today carries Theme; expands as components grow user-tunable knobs
pkg/ansi CellColor(n, text) for foreground-only ANSI in table cells (or any context that wraps content in its own SGR with a background); the foreground-only \x1b[39m reset preserves the outer background where lipgloss's full \x1b[0m would clobber it

Components own their pane. Every interactive component (pane, filter, list, table, input, toggle, logview, tree) bundles a pane.Pane internally and returns a bordered render from View(). To label one, set its Title field — it renders on the border. Don't wrap a component in a second pane; don't render a label line above it.

All components follow the same shape:

opts := somecomp.Options{ /* zero-value fields use defaults */ }
m := somecomp.New(opts)
// in your parent's Update:
m, cmd = m.Update(msg)
// in your parent's View:
s := m.View()

Layouts

pkg/layout is a tiny declarative engine — a Node knows how to render itself at a given (w, h). Containers divide their allotment among children:

layout.VStack(
    layout.Fixed(1,  layout.Bar(&m.breadcrumb)),           // 1 row
    layout.Flex(1,   layout.HStack(                        // flex middle
        layout.Fixed(24, layout.Sized(&m.sidebar)),        // 24 cols
        layout.Flex(1,   layout.Sized(&m.body)),           // rest
    )),
    layout.Fixed(1,  layout.Bar(&m.statusbar)),            // 1 row
)
  • Fixed(n, node) reserves exactly n cells on the main axis.
  • Flex(weight, node) takes a share of what's left; sibling weights set the ratio.
  • Bar(&c) adapts any SetWidth(int) + View() component (breadcrumb, statusbar).
  • Sized(&c) adapts any SetDimensions(w,h int) + View() component (pane, list).
  • RenderFunc(func(w,h int) string) is the escape hatch — size and render inline.
  • ZStack(base, overlay) composites overlay on top; Center(w, h, node) renders node at its natural size centered within the parent's rect — the typical "modal" pattern.

Layout is pure render plumbing — it doesn't own focus or key routing.

App shell and screens

pkg/app is the standard shell for a tuilib TUI: breadcrumb + flex body

  • statusbar + theme cycling + global-key routing + auto-esc-pop. You provide a root screen.Screen and a list of themes; the shell does the rest.
m := app.New(app.Options{
    Root:     newCityList(),
    Themes:   []theme.Theme{theme.Nord(), theme.Dark()},
    Version:  "v0.1.0",
    ThemeKey: key.NewBinding(key.WithKeys("t"), key.WithHelp("t", "theme")),
})
tea.NewProgram(m, tea.WithAltScreen()).Run()

A Screen is a small interface — Title / Init / Update / Layout / Help / OnEnter / SetTheme / IsCapturingKeys. Each screen declares its own layout tree; the shell renders it inside the body rect and never asks the screen about terminal dimensions.

Nav is a stack with result passing. A child screen pops with a value:

// inside the picker's Update, on enter:
return s, screen.Pop(s.list.Selected())   // child → parent data flow

The unblocked parent receives it in OnEnter(result any):

func (s *cityDetail) OnEnter(result any) tea.Cmd {
    if tz, ok := result.(string); ok && tz != "" {
        s.chosen = tz
        s.rebuildInfo()
    }
    return nil
}

Parent → child flows the other way: construct the child with whatever arguments it needs (screen.Push(newCityDetail(city))). No special method.

Atomic replace. When you want a fresh instance of the current view (reset filter, reset scroll, refetch from scratch) without disturbing the parent below, use screen.Replace(s):

return s, screen.Replace(newCityDetail(s.city, s.t))

Replace swaps the top of the stack in one tick — no pop+push flicker, and the parent's OnEnter doesn't fire spuriously. Pass the new screen with its theme already applied (s.t in the example), same convention as Push. See examples/app/replace.

IsCapturingKeys() tells the shell when a screen owns input (e.g. filter is focused) so global keys like q, t, and esc-pop are suppressed.

Statusbar messages. Screens push transient feedback into the statusbar's center slot via tea.Cmds the shell intercepts:

// inside Update, after a successful run:
return s, app.Info("Run completed successfully.")
// or, on failure:
return s, app.Error("Error: API request failed — connection refused.")
// to wipe an existing message without setting a new one:
return s, app.ClearStatus()

The shell mirrors the message into the bar with the appropriate style (green for info, red for error). Messages auto-clear on the next keypress — the same behavior as pug's footer — so you don't have to manage their lifetime. See examples/app/status for the full pattern.

Theming

theme.Theme is a single struct of semantic color tokens (BarBG, KeyFG, Accent, BorderActive, …). Swapping themes is a one-liner — every component reads from the same palette, so nothing drifts. Built-ins include Dark, Light, Nord, Dracula, Gruvbox, Solarized, TokyoNight, CatppuccinMocha/Latte, RosePine/Dawn, OneDark, Monokai, EverforestDark, four Base16… schemes, and a Terminal() that reads the user's actual terminal palette at startup.

th := theme.Nord()
bc := breadcrumb.New(th.Breadcrumb())
p  := pane.New(th.Pane())
fl := filter.New(th.Filter())
li := list.New(th.List())
sb := statusbar.New(th.Statusbar(helpModel.ShortView(), "v0.1.0"))
Default theme via env / config

app.New resolves the initial theme by checking, in order, the env var named by Options.ThemeEnvVar, the theme: field in $XDG_CONFIG_HOME/tuilib/config.yaml (falls back to ~/.config/tuilib/config.yaml, via pkg/config), and Themes[0]. Hand the raw theme list to app.Options.Themes — the shell reorders it for you:

m := app.New(app.Options{
    Root:        newRoot(),
    Themes:      theme.All(),
    ThemeEnvVar: "MY_APP_THEME",
    // ...
})

User-side, ~/.config/tuilib/config.yaml:

theme: dracula

The library never writes the file and never creates the directory — config is opt-in. A missing file is the steady state. Unknown theme names fall through silently (typo dracla just leaves Themes[0] as the default); malformed YAML surfaces as an error only if you call config.Load directly. Leave ThemeEnvVar empty to skip the env-var step and rely on the config file alone, or set Options.SkipConfig = true to disable resolution entirely (useful for tests, or when the app should always pin to Themes[0]). The underlying theme.Resolve(themes, envVar) is still exported if you need the resolved order outside app.New.

The shared file lives in pkg/config — as other components grow user-tunable knobs (logview defaults, key remaps, custom palettes) their fields will land on config.Config and any package can consult the same file without going through pkg/theme.

Examples

Run task examples to open a launcher with a menu of demos. Select one and press enter to drill in; esc pops back to the menu. The launcher itself is just pkg/app hosting a filterable list — the same pattern every other demo uses.

Entry Demonstrates
Panes Border styles, title positions, and slot-bracket variants in one 2×2 grid
List A filterable list.Model as a single-screen app
Logview Streaming log tail with /-search, n/N jump, \ filter-mode toggle, current-line highlight
Table table.Model with sticky header, per-column widths, g/G/ctrl+u/d nav, sort via [/]/s (City + Region default-string, Population uses a custom Less to parse "8.3M"), and a Status column using ansi.CellColor so the selected-row background passes through colored cells. ANSI-aware truncation means Column.Width is the visible cell width — no escape-byte budget
Form form.Model with Text / Select / Confirm fields + submit button
Loading list, logview, and tree start in SetLoading(true); staggered tea.Tick delays simulate fetches. Press r to refetch.
Drilldown Master-detail with async fetches at both levels, plus a third level via push. Cities list loads on Init; enter on a city fetches its attributes into the right pane (reqID-tagged so stale results are dropped); enter on a focused attribute pushes a child screen. Esc pops back with parent state intact.
Runner Pick a command, hand the terminal to it ($EDITOR, less, man, htop), return on exit. Last entry uses RunWithNotice to print "connecting…" during the handoff
Runlog Stream a subprocess's stdout/stderr into a logview pane; tab focus between picker and log, x to kill
Tree Synthetic project tree with cursor, expand/collapse (←→/space), /-search, and \ filter mode that hides non-matching subtrees. Leaves carry colored status icons (lipgloss-rendered) so the row highlight stays intact across ANSI segments
Themes Live palette picker — cursor re-skins the whole app via app.SetTheme
Layouts Five sub-screens, each with a different layout.Node tree
Stack Parent→child via constructor, child→parent via Pop(result) + OnEnter
Focus Multi-component focus cycling — tab/shift-tab between input + list + toggle, with Help() updating per focused component
Gate A root screen that pushes a login form on first OnEnter; submit pops back with creds, L re-pushes for logout
Tabs Three sub-screens (filterable list / streaming logview / counter) behind one tab strip; switch via shift+arrows or 1/2/3. Each body keeps its own state across switches; the logview keeps streaming while you're on another tab
Status Screens emit app.Info / app.Error / app.ClearStatus as tea.Cmds; the shell mirrors the result into the statusbar's center slot. Auto-clears on the next keypress
Confirm A pkg/confirm modal overlaid on a list via ZStack + Center. Press d on a file to bring up the dialog; on confirm the file is removed and the outcome is reported via app.Info. Demonstrates the message-driven result flow
Alert A list of mock operations; some succeed (statusbar info), some fail with an error-tinted pkg/alert modal whose border is overridden with theme.ErrorBG. Contrasts the lightweight statusbar pattern with the modal "stop and acknowledge" pattern
Poll pkg/poll drives a 2s tick that mutates a synthetic job list; SetKeyedItems keeps the cursor on the same job ID across every refresh even as statuses flip and the list reorders. p pauses, r refreshes now, +/- adjust cadence; the pane title shows "refreshed Xs ago"
PollTable pkg/poll + pkg/table SetKeyedRows on a deployments table — Sync/Health/Replicas drift each tick and dirty rows float to the top, but the cursor sticks to the same deployment ID. Same p/r/+/- keys; standard filter (env:prod, health:~degraded) and sort ([/]/s) still work
Metrics pkg/metrics inline primitives composed into a polled deployments table — Ratio for replica counts, Badge for pod-state breakdown, Bar+percent for CPU, Spark for 24s CPU history. Demonstrates the primitives plug into existing components without a custom layout

Each entry is a package under examples/<area>/<name>/ that exports New(theme.Theme) screen.Screen. The launcher imports them all and pushes the chosen one onto its stack.

Learning the library

For humans
  1. Run the launcher. task examples, then drill into Stack (nav + data flow), Layouts (layout primitives), or Themes (live palette preview). Each entry is self-contained and shows one idiom.
  2. Read the package doc comment. Every pkg/*/*.go opens with a paragraph explaining what the component is and when to use it. go doc ./pkg/pane prints it.
  3. Read the Options fields. Every field on every Options struct has a comment describing its default and when to override. go doc ./pkg/list.Options is the fastest way to see the full configuration surface.
  4. Copy an example, then delete. Start from the closest example, strip what you don't need, and the idioms come along for the ride.
For agents

Read CLAUDE.md first — it's the rules-and-anti-patterns brief that keeps generated code consistent with the library's design. Claude Code auto-loads it; other agents should read it before writing any tuilib code.

Beyond that, the library is structured to be discoverable by reading, not by convention. In order of signal density:

  1. CLAUDE.md — the rules, anti-patterns, and layout/nav idioms.
  2. go doc ./pkg/<name> — package overview + every exported symbol with its doc comment. The most complete single source.
  3. pkg/<name>/<name>.go top-of-file comment — the "what and why."
  4. Options struct field comments — the configurability surface.
  5. examples/<area>/<name>/<name>.go — each example is a package exposing New(theme.Theme) screen.Screen. Read examples/launcher/main.go to see the composition pattern (list of examples as a menu screen).
  6. pkg/theme/theme.go — the Theme struct's field comments are the semantic color vocabulary shared across every component.

A good first move for any new task is: find the closest example, read it end-to-end, then adapt.

Project layout

Follows golang-standards/project-layout:

  • pkg/ — public components (import surface for consumers)
  • internal/ — private helpers not exported
  • cmd/ — demo binaries
  • examples/launcher/ — the single entry point (task examples)
  • examples/<area>/<name>/ — each demo as a package exposing New()
  • docs/ — long-form usage notes

CI

GitHub Actions (.github/workflows/ci.yml) runs go build, go vet, and go test on every push / PR. On merge to master, the release job auto-tags with a semver bump: patch if bug/bugfix/fix appears in the branch name or merge-commit message, minor otherwise. Starts at 0.1.0, no v prefix.

Directories

Path Synopsis
examples
app/focus
Package focus demonstrates a screen with multiple components and tab/shift-tab focus cycling.
Package focus demonstrates a screen with multiple components and tab/shift-tab focus cycling.
app/gate
Package gate demonstrates a one-shot pre-screen flow: a "gated" root screen that pushes a login form on first activation, receives the credentials when the form pops, and re-pushes the form on logout.
Package gate demonstrates a one-shot pre-screen flow: a "gated" root screen that pushes a login form on first activation, receives the credentials when the form pops, and re-pushes the form on logout.
app/layouts
Package layouts demonstrates that pkg/layout can describe arbitrary compositions without the caller doing "m.h-2" math.
Package layouts demonstrates that pkg/layout can describe arbitrary compositions without the caller doing "m.h-2" math.
app/replace
Package replace demonstrates screen.Replace — atomic top-of-stack swap.
Package replace demonstrates screen.Replace — atomic top-of-stack swap.
app/stack
Package stack demonstrates a screen stack with data flowing in two directions:
Package stack demonstrates a screen stack with data flowing in two directions:
app/status
Package status demonstrates how a screen sends transient feedback to the app's statusbar.
Package status demonstrates how a screen sends transient feedback to the app's statusbar.
app/tabs
Package tabs demonstrates pkg/tab as the layout primitive of a host screen.
Package tabs demonstrates pkg/tab as the layout primitive of a host screen.
data/alert
Package alert demonstrates pkg/alert in the standard modal pattern, alongside the lighter app.Info / app.Error statusbar messages.
Package alert demonstrates pkg/alert in the standard modal pattern, alongside the lighter app.Info / app.Error statusbar messages.
data/confirm
Package confirm demonstrates pkg/confirm in the standard modal pattern: a list of files, press d to delete; a confirm dialog overlays the list via layout.ZStack(base, layout.Center(...)).
Package confirm demonstrates pkg/confirm in the standard modal pattern: a list of files, press d to delete; a confirm dialog overlays the list via layout.ZStack(base, layout.Center(...)).
data/drilldown
Package drilldown demonstrates a master-detail screen with async fetches at both levels, plus a third level via push:
Package drilldown demonstrates a master-detail screen with async fetches at both levels, plus a third level via push:
data/form
Package form demonstrates pkg/form: a vertical form with Text, Select, and Confirm fields.
Package form demonstrates pkg/form: a vertical form with Text, Select, and Confirm fields.
data/inspector
Package inspector demonstrates pkg/inspector — a two-column label/value viewer with expand/collapse for nested structured records.
Package inspector demonstrates pkg/inspector — a two-column label/value viewer with expand/collapse for nested structured records.
data/list
Package list demonstrates a filterable list.Model as a single-screen app.
Package list demonstrates a filterable list.Model as a single-screen app.
data/loading
Package loading demonstrates pkg/pane's loading state across a list, a logview, and a tree.
Package loading demonstrates pkg/pane's loading state across a list, a logview, and a tree.
data/logview
Package logview demonstrates pkg/logview as a single-screen app: a synthetic log stream you can search ("/"), jump between matches (n/N), scroll (pgup/pgdn/arrows), and pin (G to follow, g to top).
Package logview demonstrates pkg/logview as a single-screen app: a synthetic log stream you can search ("/"), jump between matches (n/N), scroll (pgup/pgdn/arrows), and pin (G to follow, g to top).
data/metrics
Package metrics demonstrates pkg/metrics inline primitives — Badge, Ratio, Bar, Spark — composed into a polled deployments table.
Package metrics demonstrates pkg/metrics inline primitives — Badge, Ratio, Bar, Spark — composed into a polled deployments table.
data/poll
Package poll demonstrates pkg/poll driving auto-refresh of a list, with keyed items so the user's cursor survives every refresh even as the underlying set churns.
Package poll demonstrates pkg/poll driving auto-refresh of a list, with keyed items so the user's cursor survives every refresh even as the underlying set churns.
data/polltable
Package polltable demonstrates pkg/poll driving auto-refresh of a table.Model, with SetKeyedRows pinning the cursor to the same row by Key across every refresh.
Package polltable demonstrates pkg/poll driving auto-refresh of a table.Model, with SetKeyedRows pinning the cursor to the same row by Key across every refresh.
data/runlog
Package runlog demonstrates streaming a subprocess's stdout/stderr into pkg/logview, with tab-cycling focus between a command picker on the left and the logview on the right.
Package runlog demonstrates streaming a subprocess's stdout/stderr into pkg/logview, with tab-cycling focus between a command picker on the left and the logview on the right.
data/runner
Package runner demonstrates pkg/runner: pick an interactive subprocess from a list, hand the terminal to it, and return to the TUI on exit.
Package runner demonstrates pkg/runner: pick an interactive subprocess from a list, hand the terminal to it, and return to the TUI on exit.
data/table
Package table demonstrates pkg/table — a filterable cursor-driven tabular view with a sticky header.
Package table demonstrates pkg/table — a filterable cursor-driven tabular view with a sticky header.
data/tree
Package tree demonstrates pkg/tree as a single-screen app: a synthetic project layout you can expand/collapse, search ("/"), and filter ("\") down to matching subtrees.
Package tree demonstrates pkg/tree as a single-screen app: a synthetic project layout you can expand/collapse, search ("/"), and filter ("\") down to matching subtrees.
launcher command
Launcher: the single entry point for tuilib examples.
Launcher: the single entry point for tuilib examples.
pane/showcase
Package showcase demonstrates pane border styles, title positions, and slot-bracket variants in a single screen.
Package showcase demonstrates pane border styles, title positions, and slot-bracket variants in a single screen.
themecheck
Package themecheck is an interactive theme picker.
Package themecheck is an interactive theme picker.
pkg
alert
Package alert provides a modal acknowledgement dialog component.
Package alert provides a modal acknowledgement dialog component.
ansi
Package ansi holds small, dependency-free ANSI helpers for the corners of the library where lipgloss's full \x1b[0m reset is the wrong tool — most notably cells inside a styled row (e.g.
Package ansi holds small, dependency-free ANSI helpers for the corners of the library where lipgloss's full \x1b[0m reset is the wrong tool — most notably cells inside a styled row (e.g.
app
Package app is the standard TUI shell: a breadcrumb header, a flex body that renders the active screen's layout, and a statusbar footer.
Package app is the standard TUI shell: a breadcrumb header, a flex body that renders the active screen's layout, and a statusbar footer.
breadcrumb
Package breadcrumb renders a single-line breadcrumb trail like "Root › Modules › dev" for use as a TUI header bar.
Package breadcrumb renders a single-line breadcrumb trail like "Root › Modules › dev" for use as a TUI header bar.
config
Package config carries the user-supplied YAML config that any tuilib component can consult.
Package config carries the user-supplied YAML config that any tuilib component can consult.
confirm
Package confirm provides a modal yes/no dialog component.
Package confirm provides a modal yes/no dialog component.
filter
Package filter provides a single-line textinput wrapped in a pane — the "press / to search, enter to commit, esc to clear" pattern every TUI eventually needs.
Package filter provides a single-line textinput wrapped in a pane — the "press / to search, enter to commit, esc to clear" pattern every TUI eventually needs.
form
Package form is a vertical form component: a sequence of Text, Select, and Confirm fields with tab/shift-tab focus cycling and a submit button.
Package form is a vertical form component: a sequence of Text, Select, and Confirm fields with tab/shift-tab focus cycling and a submit button.
help
Package help renders a toggleable help overlay showing key bindings, modeled on pug's help widget.
Package help renders a toggleable help overlay showing key bindings, modeled on pug's help widget.
input
Package input provides a single-line text input wrapped in a pane — a theme-styled, bordered text field with a title slot on the border.
Package input provides a single-line text input wrapped in a pane — a theme-styled, bordered text field with a title slot on the border.
inspector
Package inspector provides a two-column label/value viewer for structured records — pod specs, REST responses, Prefect run details, any "show me the fields of this thing" surface.
Package inspector provides a two-column label/value viewer for structured records — pod specs, REST responses, Prefect run details, any "show me the fields of this thing" surface.
layout
Package layout is a tiny declarative engine for composing Bubble Tea view strings.
Package layout is a tiny declarative engine for composing Bubble Tea view strings.
list
Package list provides a cursor-driven, optionally filterable list inside a bordered pane.
Package list provides a cursor-driven, optionally filterable list inside a bordered pane.
logview
Package logview is a streaming text viewer with incremental search and keyboard navigation, wrapped in a pane.
Package logview is a streaming text viewer with incremental search and keyboard navigation, wrapped in a pane.
metrics
Package metrics renders small inline summaries — status badges, ratios, progress bars, sparklines — sized to fit inside list rows, table cells, or inspector values without breaking outer SGR state.
Package metrics renders small inline summaries — status badges, ratios, progress bars, sparklines — sized to fit inside list rows, table cells, or inspector values without breaking outer SGR state.
pane
Package pane provides a bordered, titled, scrollable region for Bubble Tea TUIs.
Package pane provides a bordered, titled, scrollable region for Bubble Tea TUIs.
poll
Package poll is a thin interval-driven ticker for screens that need to auto-refresh remote state — k8s deployment status, Prefect runs, REST endpoints — without re-implementing the same tea.Tick + generation- counter dance every time.
Package poll is a thin interval-driven ticker for screens that need to auto-refresh remote state — k8s deployment status, Prefect runs, REST endpoints — without re-implementing the same tea.Tick + generation- counter dance every time.
runner
Package runner runs an interactive subprocess from inside a Bubble Tea program: it suspends the TUI (releasing the terminal so the subprocess can take over stdin/stdout/stderr), executes the command, then re-enters the alt-screen once the subprocess exits.
Package runner runs an interactive subprocess from inside a Bubble Tea program: it suspends the TUI (releasing the terminal so the subprocess can take over stdin/stdout/stderr), executes the command, then re-enters the alt-screen once the subprocess exits.
screen
Package screen defines a richer navigation model for pkg/app.
Package screen defines a richer navigation model for pkg/app.
statusbar
Package statusbar renders a one-line status strip with left/center/right slots, modeled on pug's footer.
Package statusbar renders a one-line status strip with left/center/right slots, modeled on pug's footer.
tab
Package tab provides a tabbed container that hosts multiple screen.Screen bodies inside one parent screen.
Package tab provides a tabbed container that hosts multiple screen.Screen bodies inside one parent screen.
table
Package table provides a cursor-driven, optionally filterable tabular view inside a bordered pane.
Package table provides a cursor-driven, optionally filterable tabular view inside a bordered pane.
theme
Package theme collapses every color used by more than one tuilib component into a single struct.
Package theme collapses every color used by more than one tuilib component into a single struct.
toggle
Package toggle provides a yes/no (boolean) selector wrapped in a pane — two labelled buttons rendered side-by-side with the active side highlighted, inside a bordered titled box.
Package toggle provides a yes/no (boolean) selector wrapped in a pane — two labelled buttons rendered side-by-side with the active side highlighted, inside a bordered titled box.
tree
Package tree provides a searchable, expand/collapse hierarchical view inside a bordered pane.
Package tree provides a searchable, expand/collapse hierarchical view inside a bordered pane.

Jump to

Keyboard shortcuts

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