tuiui package - github.com/TAbelhaDev/tabelhatuiui - Go Packages

tuiui

package module
v0.5.0 Latest Latest
Warning

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

Go to latest
Published: Sep 2, 2026 License: AGPL-3.0 Imports: 12 Imported by: 0

README

TAbelhaTuiUI

Shared theme and chrome for ianptkcs's Bubble Tea TUIs.

English · Português

Go Version Built with Bubble Tea

ko-fi


What it is

A small library holding whatever repeats across my Bubble Tea TUIs — the theme (Catppuccin Mocha + the DankMaterialShell accent), the chrome styles (header/footer/panels/modals), the layout helpers (ANSI-aware pad/wrap/truncate) and the ipc <method> [key=value...] --json convention.

Each app keeps only what is its own (model, keys, business logic); the library takes care of what everyone was redrawing from scratch in every new project.

Who uses it

Project What it is Origin
djobs TUI for scheduled jobs (systemd) migrated
tabelharadar TUI that audits git repos migrated
tabelhakanban kanban TUI born consuming it

Installation

Requires Go 1.26+.

go get github.com/TAbelhaDev/tabelhatuiui@latest

Usage

Resolve the theme in your main and use the styles wherever needed:

package main

import (
	"github.com/charmbracelet/bubbles/key"
	"github.com/TAbelhaDev/tabelhatuiui"
)

// Theme through env: reads MEUAPP_DMS_SETTINGS and MEUAPP_ACCENT (mauve by default).
var theme = tuiui.NewThemeFromEnv("MEUAPP")

var (
	keyQuit    = key.NewBinding(key.WithKeys("q", "ctrl+c"), key.WithHelp("q", "sair"))
	keyRefresh = key.NewBinding(key.WithKeys("r"), key.WithHelp("r", "atualizar"))
	keyNavL    = key.NewBinding(key.WithKeys("ctrl+l"), key.WithHelp("ctrl+l", "próx. painel"))
)

// Footer: context on the left + hints on the right, generated from the bindings.
footer := tuiui.NewFooter(keyQuit, keyRefresh, keyNavL).
	Status("3 jobs").
	Render(80, theme)

// HelpModal: the "?" every TUI has, listing the bindings in sections.
helpModal := tuiui.NewHelpModal(tuiui.HelpSection{
	Title:    "Navegação",
	Bindings: []tuiui.Binding{keyNavL},
})

func main() {
	header := theme.Header(80).Render("meu app")
	panel := theme.Panel(true).Render(tuiui.PadLines("conteúdo", 40))
	modal := theme.Modal().Render("tem certeza?")
	_ = header + footer + panel + modal
	_ = helpModal
}
Theme resolution

NewThemeFromEnv(appPrefix) is the recommended shortcut: it reads <PREFIX>_DMS_SETTINGS (default ~/.config/DankMaterialShell/settings.json) and <PREFIX>_ACCENT (default mauve) and calls ResolveTheme. For full control, ResolveTheme(settingsPath, fallbackAccent) reads the installed DankMaterialShell's settings.json and returns the hex of the accent DMS is rendering today (the same lookup DMS itself does). When DMS is not installed, or is on another theme, it falls back to the Catppuccin accent passed in.

  • NewFooter(bindings...) assembles the status/help bar. The right-hand side is generated from the bindings, so the hints never diverge from what key.Matches accepts. Status() sets the left-hand context; Render(width, theme) returns the finished line. Hints that do not fit are dropped token by token (·).
  • NewHelpModal(sections...) creates a centred overlay with the bindings in sections. Bind it to "?" (or another key), call Update() (it returns true while the modal is open — the app must not process its keys meanwhile), SetSize() on resize, and render it last in View. Sections can use BindingsFn to fetch the live bindings on every render.
Customisable keybindings (config-file-first)

KeyRegistry centralises an app's keybindings and makes the config file the source of truth: defaults registered in code + the user's overrides in <ConfigDir()>/<app>/keybindings.json. Create it, register the actions and load:

reg := tuiui.NewKeyRegistry(tuiui.ConfigPath("meuapp", "keybindings.json"))
reg.RegisterMany(
	tuiui.Action{ID: "quit", Help: "sair", Keys: []string{"q", "ctrl+c"}},
	tuiui.Action{ID: "nav", Help: "mover", Keys: []string{"ctrl+h", "ctrl+l", "ctrl+j", "ctrl+k"}, Label: "ctrl+h/j/k/l"},
)
if err := reg.Load(); err != nil { /* corrupt file: falls back to defaults */ }

The format is {"bindings": {"<id>": ["key", ...]}}, where <id> is the Action's ID; the file only exists while there is at least one override. The primary flow is edit the file and reload — expose a reload key (or call Reload() on every View()) so an external edit takes effect without a restart:

case key.Matches(msg, reg.Resolve("reload")): // e.g. "r"
	if changed, err := reg.Reload(); err != nil {
		status = theme.Error().Render("keybindings: " + err.Error())
	} else if changed {
		status = theme.Success().Render("keybindings recarregados")
	}
  • Dispatch: case key.Matches(msg, reg.Resolve("quit")):
  • Footer: tuiui.NewFooter(reg.Bindings()...) — reflects rebinds and reloads immediately.
  • Help modal: pass BindingsFn: reg.Bindings in the section.
  • Grouped hints (the canonical navigation pattern): a single action with several keys + a Label (e.g. nav with ctrl+h/l/j/k, Label: "ctrl+h/j/k/l") becomes one line in the footer/help instead of N entries; dispatch by direction stays with the app (position-based), not with the key. Use Label when the keys share one concept.
  • SettingsModal (optional convenience): tuiui.NewSettingsModal(reg) — bind it to ","/"s" to open the action list with interactive rebinding (enter rebinds, r/R reset, conflicts in red, custom marked with ). With the file-based flow above it becomes a shortcut, not the only path.
TOML config (config-file-first)

Config[T] is KeyRegistry's equivalent for the app's other preferences: defaults compiled into the code + the user's overrides in <ConfigDir()>/<app>/config.toml. Each app defines its own T; the library has no opinion about the schema.

type config struct {
	Editor string `toml:"editor"`
	Layout struct {
		SidebarWidth int `toml:"sidebar_width"`
	} `toml:"layout"`
}

var defaults = config{Editor: "nvim"} // defaults.Layout.SidebarWidth = 22, etc.

cfg := tuiui.NewConfig(tuiui.ConfigPath("meuapp", "config.toml"), defaults)
if err := cfg.Load(); err != nil { /* corrupt file: keeps the defaults */ }

width := cfg.Get().Layout.SidebarWidth

The merge is per key: the TOML only overrides the fields that appear in the file, and everything else keeps its value from defaults. Slices are replaced whole (a roots = [...] in the file swaps the entire list, it does not concatenate). A missing file is not an error — the app runs on pure defaults.

As with the keybindings, the primary flow is edit the file and reload, on the same key:

case key.Matches(msg, reg.Resolve("reload")): // f5
	kChanged, kErr := reg.Reload()
	cChanged, cErr := cfg.Reload()
	switch {
	case kErr != nil || cErr != nil:
		status = theme.Error().Render("config: " + errors.Join(kErr, cErr).Error())
	case kChanged || cChanged:
		status = theme.Success().Render("config recarregada")
	default:
		status = theme.Muted().Render("config sem mudanças")
	}

Reload() reports whether the effective config changed, and a malformed TOML returns an error while preserving the previous value — a typo mid-edit does not drop a running app to its defaults. Not every field is hot-reloadable (a database path, a worker count): Reload() says something changed, and it is up to the app to decide what to do about it.

T should be a struct of values. Pointer/map/slice fields are shared with defaults until the file overrides them, so do not mutate what Get() returns.

ConfigPath(app, file) resolves ~/.config/<app>/<file> while respecting XDG_CONFIG_HOME — use it for both config.toml and keybindings.json instead of assembling the path by hand.

Layout helpers
  • PadLines(s, width) — ANSI-aware pad/truncate of each line to an exact width (needed for panel borders to line up).
  • WrapText(s, width) — line wrapping over plain text.
  • PadToHeight(s, lines) — pads or truncates a block to an exact height.
Semantic styles

Success (green), Warning (yellow), Error (red), Info (blue) and Muted (grey) follow Catppuccin's semantics guide — for statuses each app colours its own way but with the same meaning.

IPC
args, err := tuiui.ParseIPCArgs(os.Args[2:]) // method + filters + --json
if err != nil {
	fmt.Fprintln(os.Stderr, "uso: meuapp ipc <método> [key=value...] --json")
	fmt.Fprintln(os.Stderr, err)
	os.Exit(1)
}
// dispatch on args.Method...
os.Exit(tuiui.WriteJSON(resultado))

EnvOr, ExpandHome, HomeDir and ConfigDir close out the rest of the skeleton every app used to repeat.

Development

go test ./...

Changelog

See CHANGELOG.md for the version history.

Support the project

  • Global: ko-fi.com/ianptkcs

  • Brazil (Pix): scan the QR below or copy the code

    Pix QR
    Pix code (copy)

00020126580014BR.GOV.BCB.PIX01365ad933b0-dcdc-4525-a736-0759902aeec65204000053039865802BR5925Ian Patrick da Costa Soar6009SAO PAULO62140510tQA85x6Dov63041FB6


</details>

## License

[GNU AGPL-3.0](LICENSE) — the same license as the TUIs that use it.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ConfigDir

func ConfigDir() string

ConfigDir is the user config base (os.UserConfigDir()), falling back to ~/.config — the parent of every project's own config directory.

func ConfigPath

func ConfigPath(app, file string) string

ConfigPath is ~/.config/<app>/<file>, the canonical location of an app's own config files (config.toml, keybindings.json). It honors XDG_CONFIG_HOME through ConfigDir, so callers never need to resolve the base themselves.

func EnvOr

func EnvOr(key, fallback string) string

EnvOr returns the value of env var `key`, or `fallback` when it's unset or empty.

func ExpandHome

func ExpandHome(path string) string

ExpandHome expands a leading "~" or "~/" into the user's home directory, leaving any other path untouched.

func HomeDir

func HomeDir() string

HomeDir is the current user's home directory, the base most config/data paths are anchored to. Falls back to $HOME when UserHomeDir fails.

func PadLines

func PadLines(s string, width int) string

PadLines pads or truncates every line of s so its ANSI-aware visible width is exactly `width` — a border box only ends up sized (and positioned) correctly if every line it wraps is uniform. The truncating side uses an ANSI-aware truncate (ansi.Truncate, not go-runewidth's): lines here can carry nested SGR codes (table header/selected-row colors, highlighted cards), and a truncate that counts escape-sequence bytes as visible width cuts real content far too early, garbling it.

func PadToHeight

func PadToHeight(s string, lines int) string

PadToHeight pads s with blank lines until it has exactly `lines` lines (or truncates extra ones) — used for panels whose natural content is shorter than their computed box height, so a fixed-height neighbor panel's border still lines up with this one's bottom border.

func WrapText

func WrapText(s string, width int) string

WrapText word-wraps plain text (no embedded ANSI — see Panel's comment on why Width()-based wrapping doesn't mix with already-styled content) to width, so long natural-language strings break onto new lines instead of being cut short with "…" by PadLines.

func WriteJSON

func WriteJSON(v any) int

WriteJSON pretty-prints v as JSON to stdout, returning the process exit code (0 on success, 1 on a serialization error).

Types

type Action

type Action struct {
	ID    string
	Help  string
	Keys  []string
	Label string
}

Action is one keybinding in a KeyRegistry: a stable ID (used as the key in the persisted config file and in Resolve calls), the help description shown in the footer/help modal, the default keys, and an optional display label (defaults to the keys joined with "/"). Label can be a compact alias — e.g. Keys {"j","k","up","down"} with Label "j/k".

type Binding

type Binding = key.Binding

Binding is the keybinding type the help modal and footer consume. It's the bubbles/key Binding, aliased so apps only need to import tabelhatuiui (the bubbles/key types stay available if an app wants to do its own matching).

type Config

type Config[T any] struct {
	// contains filtered or unexported fields
}

Config holds an app's settings, merged from compiled-in defaults and an optional TOML file. It mirrors KeyRegistry: the file on disk is the source of truth, and Reload() picks up external edits without restarting.

T must be a struct of plain values. Pointer, map and slice fields are shared with the defaults value until the file overrides them, so callers must not mutate what Get() returns.

func NewConfig

func NewConfig[T any](path string, defaults T) *Config[T]

NewConfig builds a Config for path, using defaults for every key the file leaves out. It does not touch the filesystem; call Load first.

func (*Config[T]) Get

func (c *Config[T]) Get() T

Get returns the current effective config: defaults with the file's overrides applied.

func (*Config[T]) Load

func (c *Config[T]) Load() error

Load reads the config file once. A missing file is not an error — the app runs on pure defaults. See Reload for the config-file-first flow.

func (*Config[T]) Path

func (c *Config[T]) Path() string

Path is the config file this Config reads, useful for error messages.

func (*Config[T]) Reload

func (c *Config[T]) Reload() (bool, error)

Reload re-reads the config file, making the on-disk config.toml the source of truth again — call it from the app's reload key so an external edit is picked up without restarting. It reports whether the effective config changed (a deleted file counts as a change back to pure defaults).

A malformed file returns an error and leaves the previous config in place, so a typo mid-edit never drops a running app back to defaults.

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

Footer builds the bottom status/help bar: a context string on the left (a message, a counter, "perfil: dev") and the keybinding hints on the right, generated from a slice of bubbles/key bindings so the hints can never drift out of sync with what Update() actually matches.

The layout follows what every ianptkcs TUI hand-rolled before this type existed: left and right sides separated by a run of spaces, with token-aware truncation (hints are dropped at " · " boundaries from the right, then the left side is truncated) when the line overflows.

func NewFooter

func NewFooter(bindings ...Binding) *Footer

NewFooter starts a footer whose right side is generated from the given keybindings. Disabled bindings are skipped.

func (*Footer) Line

func (f *Footer) Line(width int) string

Line returns the footer's unstyled content, sized to fit a Footer(width) render — that is, trimmed to width-4 columns (the Footer style's horizontal padding + border consume the other 4). The line never wraps: whatever doesn't fit gets dropped or truncated.

func (*Footer) Render

func (f *Footer) Render(width int, theme Theme) string

Render renders the footer fully styled with the given theme. Drop the result straight into the View output.

func (*Footer) Status

func (f *Footer) Status(s string) *Footer

Status sets the left-side context text. It can carry ANSI (callers pass already-styled message strings); the width math is ANSI-aware.

type HelpModal

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

HelpModal is a centered overlay that lists every keybinding in the app, grouped by section — the "?" view every vim/tmux-ish TUI has. Build it once with the app's sections, bind "?" to Toggle(), forward Update() results, and render it last in View (on top of the app).

Scrolls with j/k (or arrows), closes with q/esc. Sizes itself from the latest SetSize call and centers itself over the whole screen.

func NewHelpModal

func NewHelpModal(sections ...HelpSection) *HelpModal

NewHelpModal creates a help modal from the given sections. Bindings that are disabled (or have no keys) are skipped at render time.

func (*HelpModal) Close

func (m *HelpModal) Close()

func (*HelpModal) Open

func (m *HelpModal) Open()

Open and Close explicitly control visibility.

func (*HelpModal) SetSize

func (m *HelpModal) SetSize(width, height int)

SetSize records the current viewport so the modal can size/center itself. Call it from the app's WindowSizeMsg handler.

func (*HelpModal) Toggle

func (m *HelpModal) Toggle()

Toggle opens the modal if it's closed and closes it if it's open.

func (*HelpModal) Update

func (m *HelpModal) Update(msg tea.Msg) bool

Update handles keys while the modal is open and reports whether the message was consumed. When visible, every message is consumed — the app must not process its own keys while the modal is up.

func (*HelpModal) View

func (m *HelpModal) View(theme Theme) string

View renders the modal overlay centered on the screen, or "" when closed. Render it last in the app's View so it sits on top of everything else.

func (*HelpModal) Visible

func (m *HelpModal) Visible() bool

Visible reports whether the modal is currently open.

type HelpSection

type HelpSection struct {
	Title      string
	Bindings   []Binding
	BindingsFn func() []Binding
}

HelpSection is one titled group of keybindings in the help modal — for example "Navegação" with the panel-switching keys, "Ações" with the per-app commands. Bindings are static; BindingsFn (if set) supplies them live at render time — pass one backed by a KeyRegistry so the help modal reflects rebinds instantly.

type IPCArgs

type IPCArgs struct {
	Method  string
	Filters map[string]string
	JSON    bool
}

IPCArgs is the parsed form of a `bin ipc <método> [key=value...] --json` invocation — the scriptable-data-source convention dcal/djobs/tabelharadar all share, so a shell script or an LLM can ask a TUI's data without going through the interface itself.

func ParseIPCArgs

func ParseIPCArgs(args []string) (*IPCArgs, error)

ParseIPCArgs parses the args after `ipc`. It returns an error for an unknown arg (anything that's neither --json nor key=value) or a missing --json flag; the caller is expected to print its own usage line with it.

type KeyRegistry

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

KeyRegistry is the single source of truth for an app's keybindings: defaults registered in code, optional per-action overrides persisted to a JSON file, and live resolution so dispatch/footer/help/settings always see the same effective binding. Create one, Register every action, Load(), then Resolve in Update and feed Bindings() to Footer/HelpModal.

The config file is the primary customization flow (config-file-first): the user edits keybindings.json and the app calls Reload() on a reload key (or per View) to pick the edit up without restarting. The persisted format is a flat map of action ID to the custom keys; the file is written only when at least one override exists (all-resets delete it), and is removed when the last override is cleared.

func NewKeyRegistry

func NewKeyRegistry(path string) *KeyRegistry

NewKeyRegistry creates an empty registry that persists overrides to path.

func (*KeyRegistry) Actions

func (r *KeyRegistry) Actions() []Action

Actions returns every registered action with its CURRENT (effective) keys, in registration order, for rendering the settings list.

func (*KeyRegistry) Bindings

func (r *KeyRegistry) Bindings() []key.Binding

Bindings returns the effective binding for every registered action, in registration order — feed this to Footer/HelpModal.

func (*KeyRegistry) CustomCount

func (r *KeyRegistry) CustomCount() int

CustomCount is the number of actions with a custom override.

func (*KeyRegistry) IsCustom

func (r *KeyRegistry) IsCustom(id string) bool

IsCustom reports whether an action currently has a custom override.

func (*KeyRegistry) Load

func (r *KeyRegistry) Load() error

Load reads custom overrides from the config file once. A missing file is not an error (no overrides yet). See Reload for the config-file-first flow.

func (*KeyRegistry) Register

func (r *KeyRegistry) Register(a Action) *KeyRegistry

Register adds (or replaces) an action with its default keys and help. Registration order is kept and used by Bindings()/Actions().

func (*KeyRegistry) RegisterMany

func (r *KeyRegistry) RegisterMany(actions ...Action) *KeyRegistry

RegisterMany is a convenience for registering a slice of actions in order.

func (*KeyRegistry) Reload

func (r *KeyRegistry) Reload() (bool, error)

Reload re-reads the config file, making the on-disk keybindings.json the source of truth again — call it from the app's reload key (or once per View) so an external edit to the file is picked up without restarting. It reports whether the overrides changed on disk since the last load/save (a deleted file counts as a change back to pure defaults).

func (*KeyRegistry) Reset

func (r *KeyRegistry) Reset(id string) error

Reset restores an action to its default keys and persists the change.

func (*KeyRegistry) ResetAll

func (r *KeyRegistry) ResetAll() (int, error)

ResetAll restores every action to its defaults (deleting the config file) and returns the number of overrides that were cleared.

func (*KeyRegistry) Resolve

func (r *KeyRegistry) Resolve(id string) key.Binding

Resolve returns the effective binding for an action — custom keys when an override exists, otherwise the defaults. Unknown IDs resolve to a disabled empty binding.

func (*KeyRegistry) Save

func (r *KeyRegistry) Save() error

Save persists the current overrides. With nothing overridden it deletes the config file so the app reads as pure defaults.

func (*KeyRegistry) Set

func (r *KeyRegistry) Set(id string, keys ...string) error

Set overrides the keys of an action and immediately persists the change. It fails (without saving) when a key is already bound to another action.

type SettingsModal

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

SettingsModal is the keybinding editor: a centered overlay listing every registered action with its current keys, letting the user rebind (enter → "press a key"), reset an action (r) or everything (R). Overrides go straight to the KeyRegistry, which persists them.

Built on top of a KeyRegistry, so the footer, the help modal and the key dispatch all pick up changes immediately. Bind a key (e.g. "," or "s") to Toggle(), forward Update() results, and render it last in View.

func NewSettingsModal

func NewSettingsModal(r *KeyRegistry) *SettingsModal

NewSettingsModal creates a settings modal editing the given registry.

func (*SettingsModal) Close

func (m *SettingsModal) Close()

func (*SettingsModal) Open

func (m *SettingsModal) Open()

Open and Close explicitly control visibility.

func (*SettingsModal) SetSize

func (m *SettingsModal) SetSize(width, height int)

SetSize records the current viewport so the modal can size/center itself.

func (*SettingsModal) Toggle

func (m *SettingsModal) Toggle()

Toggle opens the modal if closed and closes it if open.

func (*SettingsModal) Update

func (m *SettingsModal) Update(msg tea.Msg) bool

Update handles keys while the modal is open and reports whether the message was consumed. While visible, every message is consumed — the app must not process its own keys.

func (*SettingsModal) View

func (m *SettingsModal) View(theme Theme) string

View renders the settings overlay centered on the screen, or "" when closed. Render it last in the app's View.

func (*SettingsModal) Visible

func (m *SettingsModal) Visible() bool

Visible reports whether the modal is currently open.

type Theme

type Theme struct {
	Base     lipgloss.Color
	Mantle   lipgloss.Color
	Surface0 lipgloss.Color
	Surface1 lipgloss.Color
	Overlay0 lipgloss.Color
	Overlay1 lipgloss.Color
	Text     lipgloss.Color
	Subtext0 lipgloss.Color

	// Primary mirrors the installed DankMaterialShell's own configured
	// accent (falling back to a manually chosen Catppuccin accent) — see
	// resolvePrimaryHex. Consumers read the same DMS settings.json djobs and
	// tabelharadar do, so every tool's chrome matches whatever accent DMS is
	// set to.
	Primary lipgloss.Color

	Red      lipgloss.Color
	Green    lipgloss.Color
	Yellow   lipgloss.Color
	Blue     lipgloss.Color
	Pink     lipgloss.Color
	Lavender lipgloss.Color
}

Theme is the resolved Catppuccin Mocha palette plus the DMS accent used as Primary — the shared chrome every ianptkcs TUI renders with. Colors follow the official semantic guide: https://github.com/catppuccin/catppuccin/blob/main/docs/style-guide.md

func NewThemeFromEnv

func NewThemeFromEnv(appPrefix string) Theme

NewThemeFromEnv builds the theme like ResolveTheme, but sources the DMS settings path and the fallback accent from environment variables derived from appPrefix — the same lookup every ianptkcs TUI's theme.go hand-rolled:

<PREFIX>_DMS_SETTINGS  (default ~/.config/DankMaterialShell/settings.json)
<PREFIX>_ACCENT        (default "mauve")

func ResolveTheme

func ResolveTheme(settingsPath, fallbackAccent string) Theme

ResolveTheme builds the theme: Primary comes from the DMS settings.json at settingsPath (empty string means "not resolvable", falling back to the Catppuccin accent fallbackAccent).

func (Theme) Dim

func (t Theme) Dim() lipgloss.Style

func (Theme) Error

func (t Theme) Error() lipgloss.Style

func (Theme) Footer

func (t Theme) Footer(width int) lipgloss.Style

Footer renders the bottom help/status bar: mantle background, subtext text, full width, flush against the terminal edges.

func (Theme) Header

func (t Theme) Header(width int) lipgloss.Style

Header renders the top status bar: primary accent background, base text, full width, flush against the terminal edges. Horizontal padding gives the title an inset from the edges.

func (Theme) Info

func (t Theme) Info() lipgloss.Style

func (Theme) Modal

func (t Theme) Modal() lipgloss.Style

Modal is a centered overlay box with an accent (Primary) border — used by djobs for its form/confirm dialogs, generic enough to live in the lib.

func (Theme) Muted

func (t Theme) Muted() lipgloss.Style

Muted is the "no longer relevant" fallback — same as Dim but one step more visible (Overlay1), used for stale/secondary content.

func (Theme) Panel

func (t Theme) Panel(focused bool) lipgloss.Style

Panel intentionally has no Width(): calling Width() on a style makes lipgloss re-wrap its content, and that wrap logic miscounts lines that already carry their own nested ANSI (a table's selected-row highlight, a highlighted card), breaking alignment. Content is pre-padded to a uniform width with PadLines instead, so the border ends up sized correctly on its own. focused switches the border to Primary (also used for headers/ titles), so the currently-navigable panel is obvious.

func (Theme) Success

func (t Theme) Success() lipgloss.Style

Semantic status styles follow the Catppuccin style guide's meanings: Success for active/ok, Warning for paused/delayed, Error for a hard problem, Info for a neutral "done" state. Background is set to Base so the colored text reads correctly wherever it's dropped in (a table cell, a description line, a modal) — djobs' colorizeStatusColumn needs this.

func (Theme) Title

func (t Theme) Title() lipgloss.Style

Title is a panel's first line, bold in the primary accent. Background is set explicitly for the same reason Panel's is: its own reset must not blank out the panel's background for that line.

func (Theme) Warning

func (t Theme) Warning() lipgloss.Style

Jump to

Keyboard shortcuts

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