huh
A simple, powerful Go library for building interactive terminal forms and prompts, with theming and accessibility built in.
Repository Health
Technical Analysis
huh is a Go library from Charm for building interactive terminal forms and prompts. Forms are composed from groups of fields — input, text, select, multi-select, confirm, and note — and can either run standalone as a blocking call or be embedded directly inside a Bubble Tea application as a nested model. Fields support validation functions, dynamic titles and options that recompute based on other field values, and a first-class accessible mode that drops the TUI in favor of plain sequential prompts for screen readers.
The library ships five built-in themes (Charm, Dracula, Catppuccin, Base 16, Default) built on Lip Gloss styling, and a companion spinner package for indicating background activity once a form is submitted. It’s widely used across the Charm ecosystem and by third-party Go CLIs — including GitHub’s own gh extensions — as the default way to collect structured, validated input from a terminal user without hand-rolling prompt loops.
What You Get
- Six field types — Input, Text, Select, MultiSelect, Confirm, and FilePicker — each configurable via a fluent, chainable builder API
- Forms organized into Groups (pages) with automatic navigation, per-field validation, and error display before advancing
- Dynamic fields via TitleFunc/OptionsFunc/DescriptionFunc that recompute based on bindings to other field values, with built-in caching
- Five predefined themes (Charm, Dracula, Catppuccin, Base 16, Default) plus a full custom Lip Gloss theme abstraction
- A dedicated accessible mode (WithAccessible) that renders plain sequential prompts instead of a TUI for screen-reader users
- A standalone spinner sub-package for showing background activity with either an Action-callback style or a context-driven style
- Native embedding inside Bubble Tea apps as a nested tea.Model, or standalone blocking use via form.Run() / field.Run()
Common Use Cases
- Interactive CLI onboarding — collecting project name, environment, and configuration choices when scaffolding a new project
- Git/GitHub workflow prompts — commit message, PR description, and reviewer selection forms in custom Git tooling
- Ordering/checkout-style demos and internal tools — multi-group forms with conditional, dynamically-populated fields (e.g. country → state)
- Accessible terminal surveys and setup wizards where screen-reader compatibility is a hard requirement
- Adding structured, validated user input to existing Bubble Tea TUIs without building a custom form component from scratch
Under The Hood
Architecture
huh centers on a Form (form.go) that owns a selector.Selector[*Group] — a small internal cursor type (internal/selector/selector.go) shared by both forms-over-groups and groups-over-fields navigation. Each Group similarly wraps a selector.Selector[Field], and every field type (field_input.go, field_select.go, field_multiselect.go, field_confirm.go, field_text.go, field_filepicker.go, field_note.go) implements a common Field interface that embeds a compat.Model (Bubble Tea’s Model, aliased through internal/compat to bridge the v1/v2 Bubble Tea API) plus Blur/Focus/Error/Run/RunAccessible/Skip/Zoom. This lets Form.Update in form.go dispatch tea.Msg values down through the active group to the active field uniformly, and lets any field also run standalone via its own Run(). Dynamic values (titles, descriptions, options) are modeled by a generic Eval[T] type (eval.go) that hashes its declared bindings via mitchellh/hashstructure and only recomputes + emits an update message when the hash changes, with an in-memory cache keyed by hash. Accessible mode is a genuinely separate code path (internal/accessibility) rather than a rendering flag on the TUI path, so screen-reader support isn’t an afterthought bolted onto the Bubble Tea view.
Tech Stack
The module (charm.land/huh/v2, go.mod) targets Go 1.25 and sits squarely inside the Charm terminal ecosystem: charm.land/bubbletea/v2 for the Elm-style update loop, charm.land/bubbles/v2 for shared help/viewport/key components, and charm.land/lipgloss/v2 for styling — plus charmbracelet/x/ansi, x/term, and x/xpty for lower-level terminal and pty handling. Theming pulls in catppuccin/go for one of its five preset palettes, and dynamic-field caching depends on mitchellh/hashstructure/v2 for stable struct hashing. There’s no external build tooling beyond go build/go test and a Makefile; CI (.github/workflows/lint.yml, build.yml) delegates to Charm’s own shared charmbracelet/meta reusable workflows rather than a bespoke pipeline.
Code Quality
huh_test.go carries 32 top-level test functions plus a dedicated zz_resize_width_test.go and a spinner_test.go (12 tests) in the spinner sub-package — solid coverage of form/group/field state transitions, though there’s no single dominant assertion framework beyond stdlib testing. Error handling is explicit and typed: sentinel errors (ErrUserAborted, ErrTimeout, ErrTimeoutUnsupported) are exported for callers to check with errors.Is, and field-level validation returns plain error values surfaced through Field.Error() rather than being swallowed. Naming is consistent and idiomatic Go (exported With* option methods, *Func variants for dynamic fields), and golangci-lint runs in CI against a shared Charm config, giving the codebase consistent static-analysis coverage across the whole org rather than a one-off local config.
API Design
The public API is a fluent, chainable builder (huh.NewInput().Title(...).Validate(...).Value(&v)) that reads close to natural language and keeps boilerplate low — a single field can run standalone with .Run(), or be composed into NewGroup(...) and NewForm(...) for multi-page flows, so the same field types scale from a one-line prompt to a full wizard without an API switch. Generics (Select[T], MultiSelect[T]) let option values be any comparable type instead of forcing string-only APIs, and *Func variants (TitleFunc, OptionsFunc) make dependent/dynamic fields an explicit, well-documented escape hatch rather than a workaround. Documentation is strong for a library this size — a long, example-driven README, a dedicated UPGRADE_GUIDE_V2.md for the v1→v2 migration, and 20+ runnable examples under examples/ covering everything from basic forms to Bubble Tea embedding and accessibility.
Used by 6 apps in this directory
Flipt
Devops · Developer Tools
Git-native feature flag platform that stores, versions, and deploys feature toggles directly in your own Git repositories with no external database required.
Hatchet
AI Development · Developer Tools · Automation
A Postgres-backed orchestration engine for background tasks, AI agents, and durable workflows that replaces Redis queues and multi-datastore durable execution platforms with a single self-hostable service.
cli
Developer Tools · Team Chat
The official Lark/Feishu CLI tool built for both humans and AI agents, delivering 200+ commands and 26 AI agent skills across 18 business domains from messaging and calendar to docs, sheets, and approvals.
Pangolin
Networking
An open-source, identity-based zero-trust remote access platform built on WireGuard — a self-hostable alternative to Cloudflare Tunnel and Twingate with SSO, OIDC, and tunneled reverse proxying.
Teleport
Security · Authentication
Zero-trust infrastructure access platform that replaces credentials and VPNs with short-lived certificates, SSO, and identity-aware proxies for SSH, Kubernetes, databases, RDP, and AI agents.
Tusk
Developer Tools · AI Code Assistants · Devops
Record live API traffic and replay it as deterministic, sandboxed tests, plus AI code review and unit test generation, all from one Go CLI built by YC W2024's Use-Tusk.