A simple and powerful library for building interactive forms in the terminal. Powered by Bubble Tea.
The above example is running from a single Go program (source).
huh?
provides a straightforward API to build forms and prompt users for input.
For this tutorial, we're building a Burger order form. Lets start by importing the dependencies that we'll need.
package main
import (
"log"
"github.com/charmbracelet/huh"
)
huh
allows you to define a form with multiple groups to separate field forms
into pages. We will set up a form with three groups for the customer to fill
out.
form := huh.NewForm(
// Prompt the user to choose a burger.
huh.NewGroup(
huh.NewSelect[string]().
Options(
huh.NewOption("Charmburger Classic", "classic"),
huh.NewOption("Chickwich", "chickwich"),
huh.NewOption("Fishburger", "Fishburger"),
huh.NewOption("Charmpossible™ Burger", "charmpossible"),
).
Title("Choose your burger").
Value(&burger),
),
// Prompt for toppings and special instructions.
// The customer can ask for up to 4 toppings.
huh.NewGroup(
huh.NewMultiSelect[string]().
Options(
huh.NewOption("Lettuce", "Lettuce").Selected(true),
huh.NewOption("Tomatoes", "Tomatoes").Selected(true),
huh.NewOption("Charm Sauce", "Charm Sauce"),
huh.NewOption("Jalapeños", "Jalapeños"),
huh.NewOption("Cheese", "Cheese"),
huh.NewOption("Vegan Cheese", "Vegan Cheese"),
huh.NewOption("Nutella", "Nutella"),
).
Title("Toppings").
Limit(4).
Value(&toppings),
),
// Gather final details for the order.
huh.NewGroup(
huh.NewInput().
Title("What's your name?").
Value(&name).
Validate(validateName),
huh.NewText().
Title("Special Instructions").
Value(&instructions).
CharLimit(400),
huh.NewConfirm().
Title("Would you like 15% off").
Value(&discount),
),
)
Finally, we can run the form:
err := form.Run()
if err != nil {
log.Fatal(err)
}
Input
: single line text inputText
: multi-line text inputSelect
: select an option from a listMultiSelect
: select multiple options from a listConfirm
: confirm an action (yes or no)
Prompt the user for a single line of text.
huh.NewInput().
Title("What's for lunch?").
Prompt("?").
Validate(isFood).
Value(&lunch)
Prompt the user for multiple lines of text.
huh.NewText().
Title("Tell me a story.").
Validate(checkForPlagiarism).
Value(&story)
Prompt the user to select from a list.
huh.NewSelect[string]().
Title("Pick a country.").
Options(
huh.NewOption("United States", "US"),
huh.NewOption("Germany", "DE"),
huh.NewOption("Brazil", "BR"),
huh.NewOption("Canada", "CA"),
).
Value(&country)
Prompt the user to select multiple options from a list.
huh.NewMultiSelect[string]().
Options(
huh.NewOption("Lettuce", "Lettuce").Selected(true),
huh.NewOption("Tomatoes", "Tomatoes").Selected(true),
huh.NewOption("Charm Sauce", "Charm Sauce"),
huh.NewOption("Jalapeños", "Jalapeños"),
huh.NewOption("Cheese", "Cheese"),
huh.NewOption("Vegan Cheese", "Vegan Cheese"),
huh.NewOption("Nutella", "Nutella"),
).
Title("Toppings").
Limit(4).
Value(&toppings)
Prompt the user to confirm an option (yes or no).
huh.NewConfirm().
Title("You sure?").
Affirmative("Yes!").
Negative("No.").
Value(&confirm)
Forms can be made accessible to screen readers through setting the
WithAccessible
option. It's useful to set this through an environment variable
or configuration option to allow the user to control whether their form is
accessible for screen readers.
form.WithAccessible(os.Getenv("ACCESSIBLE") != "")
Making the form accessible will remove redrawing and use more standard prompts to ensure that screen readers are able to dictate the information on screen correctly.
Forms can be customized through themes. You can supply your own custom theme or use the predefined themes.
There are currently four predefined themes:
Charm
Dracula
Base 16
Default
Huh additionally provides a spinner
subpackage for displaying spinners while
performing actions. It's useful to complete an action after your user completes
a form.
To get started, create a new spinner, set a title, set an action, and run the spinner:
makeBurger := func() {
//...
}
err := spinner.New().
Title("Making your burger...").
Action(makeBurger).
Run()
fmt.Println("Order up!")
Alternatively, you can also use Context
s. The spinner will stop once the
context is cancelled.
makeBurger := func() {
// ...
}
ctx, _ := context.WithTimeout(context.Background(), time.Second)
go makeBurger()
err := spinner.New().
Title("Making your burger...").
Context(ctx).
Run()
fmt.Println("Order up!")
What about Bubble Tea?
Huh doesn’t replace Bubble Tea. Rather, it is an abstraction built on Bubble Tea to make forms easier to code and implement. It was designed to make assembling powerful and feature-rich forms in Go as simple and fun as possible.
While you can use huh
as a replacement to Bubble Tea in many applications
where you only need to prompt the user for input. You can embed huh
forms in
Bubble Tea applications and use the form as a Bubble.
type Model struct {
// embed form in parent model, use as bubble.
form *huh.Form
}
func NewModel() Model {
return Model{
form: huh.NewForm(
huh.NewGroup(
huh.NewSelect[string]().
Key("class").
Options(huh.NewOptions("Warrior", "Mage", "Rogue")...).
Title("Choose your class"),
huh.NewSelect[int]().
Key("level").
Options(huh.NewOptions(1, 20, 9999)...).
Title("Choose your level"),
),
)
}
}
func (m Model) Init() tea.Cmd {
return m.form.Init()
}
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
// ...
form, cmd := m.form.Update(msg)
if f, ok := form.(*huh.Form); ok {
m.form = f
}
return m, cmd
}
func (m Model) View() string {
if m.form.State == huh.StateCompleted {
return fmt.Sprintf("You selected: %s, Lvl. %d", m.form.GetString("class"), m.form.GetInt("level"))
}
return m.form.View()
}
See the Bubble Tea example for how to embed huh
forms in Bubble
Tea applications for more advanced use cases.
We'd love to hear your thoughts on this project. Feel free to drop us a note!
huh
is inspired by the wonderful Survey library by Alec Aivazis.
Part of Charm.
Charm热爱开源 • Charm loves open source • نحنُ نحب المصادر المفتوحة