charmbracelet/huh: Terminal Forms in Go, From Group to Group
Build terminal forms and prompts 🤷🏻‍♀️
At a glance
- What is it?
- huh is a Go library for interactive terminal forms and prompts, with groups, typed fields and an accessible mode. It fits CLI tools that need structured input; it does not fit non-Go projects or non-interactive pipelines.
- Who is it for?
- Adopt huh if you ship a Go CLI that needs structured interactive input and you are willing to depend on the Charm stack (bubbles, bubbletea, lipgloss). Do not adopt it if your input path must work without a TTY, or if your project is not in Go, since the library is Go-only and the README offers no non-Go binding.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 28 days ago.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 30, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The problem huh solves: structured input inside a CLI
Command line tools that need more than a flag usually end up hand-rolling prompts. You read a line, trim it, validate it, print an error, read again. Once there are five questions and two of them are conditional, the prompt code becomes the largest file in the project. huh is a Go library that replaces that code with declared fields: a Select, a MultiSelect, an Input, a Text and a Confirm, each bound to a variable through a Value pointer. The README describes it as "a simple, powerful library for building interactive forms and prompts in the terminal."
The audience is narrow and specific. It is for Go developers writing interactive CLIs: setup wizards, first-run configuration, order or deployment flows, anything where a human answers questions at a terminal. It is not for web forms, not for parsing arguments (that is a flag or command library's job), and not for unattended scripts, because a form expects someone at the keyboard. The repository layout supports that reading: the top level is Go source files such as form.go, group.go and the field_*.go files, with examples/ holding runnable programs like examples/burger and examples/ssh-form.
Groups as pages, fields as typed values
The mechanism is a three-level structure. A form holds groups, a group holds fields, and a field holds options or a value. The README is explicit about the middle level: "huh? separates forms into groups (you can think of groups as pages)." Groups are the unit of progression, so a form with two groups asks the first group's questions, then the second's, rather than showing one long list.
Fields are generic over the answer type. huh.NewSelect[string]() stores strings; the README shows huh.NewSelect[int]() storing sauce levels as integers, with the comment that "Option values in selects and multi selects can be any type you want." Binding is done with Value(&variable), so the library writes the answer into your variable instead of returning an untyped map. Validation is a function returning an error, attached with Validate, and the README says the form "will mark erroneous fields and display error messages accordingly."
Two details are worth noting because they shape how you write code. MultiSelect accepts a Limit, and the README's example sets Limit(4) with the comment "there's a 4 topping limit!" Text accepts a CharLimit, shown as CharLimit(400). Both are field-level constraints rather than validation functions, which means the input is bounded before your Validate callback runs. Underneath, go.mod shows the library built on charm.land/bubbles/v2, charm.land/bubbletea/v2 and charm.land/lipgloss/v2, so a huh form is a Bubble Tea program whether or not you write any Bubble Tea code.
Installing huh and running a first form
huh is a Go module, so installation means adding it to a Go project. The module path in go.mod is charm.land/huh/v2, and go.mod also states go 1.25.8, so the toolchain matters before you start. The README's tutorial imports the package directly:
package main
import "charm.land/huh/v2"From there you declare variables for the answers and build the form. This is the shape of the README's burger example, trimmed to one group:
var (
burger string
sauceLevel int
name string
)
form := huh.NewForm(
huh.NewGroup(
huh.NewSelect[string]().
Title("Choose your burger").
Options(
huh.NewOption("Charmburger Classic", "classic"),
huh.NewOption("Chickwich", "chickwich"),
).
Value(&burger),
huh.NewSelect[int]().
Title("How much Charm Sauce do you want?").
Options(
huh.NewOption("None", 0),
huh.NewOption("A little", 1),
huh.NewOption("A lot", 2),
).
Value(&sauceLevel),
),
)Running it is a blocking call that returns an error, which the README handles by exiting:
err := form.Run()
if err != nil {
log.Fatal(err)
}What you should see is the first group rendered in the terminal, one field at a time, with the chosen values written into burger and sauceLevel after submission. The README also documents a shorter path for a single question: each field has its own Run method, so huh.NewInput().Title("What's your name?").Value(&name).Run() blocks until the user answers, and the README notes "this is blocking..." before printing the value. If you want to run the repository's own examples instead of writing code, the Makefile exposes targets: make burger runs examples/burger, make gh runs examples/gh, and make test runs go test ./... across the module.
Where huh is the wrong tool
The clearest boundary is interactivity. A huh form needs a terminal to draw into and a user to answer. If your program runs in CI, in a cron job, or behind a pipe with no TTY, there is nothing for the form to attach to, and the README does not describe a non-interactive mode or a way to pre-fill a form from flags. Tools that must work both ways usually need a separate flag-driven path, which means the form is an addition to your CLI, not a replacement for its argument parsing.
The second boundary is language. The module is Go, the examples are Go, and the repository contains no binding for another runtime. A Python or Rust CLI cannot use huh without embedding a Go binary or reimplementing the prompts.
The third is the dependency surface. go.mod lists bubbletea, bubbles, lipgloss, catppuccin/go, several charmbracelet/x packages, hashstructure, and a set of indirect dependencies including pty and terminfo libraries. That is a reasonable footprint for a Go TUI, but it is not a small one, and it ties your binary's rendering and terminal handling to the Charm stack's release cadence. If you only need one yes/no question, huh is more machinery than the problem deserves. The README itself points at the single-field Run shorthand for that case, but even that pulls the full module in.
There is also a version boundary. The module path carries /v2 and the repository ships UPGRADE_GUIDE_V2.md, which means v1 users have a migration ahead of them. The README does not document rollback, and the upgrade guide is the place to check what changed.
huh against Bubble Tea on its own
The natural comparison is Bubble Tea, the event-loop library huh is built on. In Bubble Tea you write a model with an Init, an Update and a View, and you handle every keypress yourself: moving a cursor, tracking which field is focused, deciding when the form is complete. That gives you total control over state and rendering, and it is the right level if your interface is not a form at all, or if the form is one screen inside a larger application.
huh inverts that. You declare what you want to ask, and the library owns focus, navigation, validation display and the transition between groups. The README notes that huh "can be integrated into a Bubble Tea application," so the two are not exclusive: the examples directory contains examples/bubbletea and examples/bubbletea-options, which suggests the intended pattern is a huh form embedded in a larger Bubble Tea program rather than a choice between them. The trade-off is control. With Bubble Tea you decide what a validation failure looks like; with huh you get the library's rendering and its theme system, and you customize through theme.go and the exported options rather than by rewriting the view.
A second comparison worth naming is gum, which appears in the examples as examples/gum. gum is a shell-level tool for the same kind of prompts, which means it works from a shell script rather than a Go program. If your CLI is a bash script, huh is not available to you and gum is the relevant tool; if your CLI is a Go binary, huh keeps the prompts in-process and typed.
Accessibility, maintenance and what the licence leaves open
The README states that huh "contains a first-class accessible mode for screen readers," and the repository carries examples/accessibility and examples/accessibility-secure-input. That is unusual for a TUI library and it is a real reason to pick huh over hand-rolled prompts, which almost never handle screen readers. The README does not enumerate which field types the accessible mode covers, so if accessibility is a requirement, the examples are the place to check before you commit to a field set.
On maintenance, the last push to the repository was on 2026-09-01. The most recent releases listed are v2.0.3, v2.0.2 and v2.0.1, all from March 2026. The repository is not archived. Those are the facts; the README does not publish a support policy or a compatibility promise beyond the v2 module path.
The licence is MIT, which permits use, modification and redistribution with the licence text retained. That is a permissive licence and it is compatible with closed-source products, but it is not legal advice and it says nothing about the licences of the dependencies in go.mod, which you should review separately if your organization audits transitive licences.
Editorial conclusion
Adopt huh if you ship a Go CLI that needs structured interactive input and you are willing to depend on the Charm stack (bubbles, bubbletea, lipgloss). Do not adopt it if your input path must work without a TTY, or if your project is not in Go, since the library is Go-only and the README offers no non-Go binding. Before committing, verify which v2 release you are pinning (v2.0.3 is the latest listed), read UPGRADE_GUIDE_V2.md if you already use v1, and confirm the accessible mode covers the field types you plan to ship.
Frequently asked questions
What is charmbracelet/huh?
It is a Go library for building interactive forms and prompts in the terminal, described in the README as "a simple, powerful library for building interactive forms and prompts in the terminal." Forms are made of groups, and groups are made of fields such as Input, Text, Select, MultiSelect and Confirm.
How do I install charmbracelet/huh in a Go project?
Add the module charm.land/huh/v2 to your Go project and import it, as the README does with import "charm.land/huh/v2". The repository's go.mod declares go 1.25.8, so check your toolchain version first.
What is the difference between charmbracelet/huh and Bubble Tea?
Bubble Tea is the event-loop library huh is built on, and huh can be integrated into a Bubble Tea application according to the README. With huh you declare fields and the library handles focus, validation display and group transitions; with Bubble Tea you write the model and update loop yourself.
Can I use charmbracelet/huh for a single prompt instead of a full form?
Yes. The README notes that each field has a Run method that works as a shorthand for quick input, so huh.NewInput().Title("What's your name?").Value(&name).Run() blocks until the user answers.
Does charmbracelet/huh work with screen readers?
The README states that huh contains a first-class accessible mode for screen readers, and the repository includes examples/accessibility and examples/accessibility-secure-input. The README does not list which field types the accessible mode covers.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/charmbracelet-huh)