Skip to content

charmbracelet/huh

Repository files navigation

Huh?

A simple and powerful library for building interactive forms in the terminal. Powered by Bubble Tea.

Running a burger form

The above example is running from a single Go program (source).

Tutorial

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)
}

Field Reference

  • Input: single line text input
  • Text: multi-line text input
  • Select: select an option from a list
  • MultiSelect: select multiple options from a list
  • Confirm: confirm an action (yes or no)

Input

Prompt the user for a single line of text.

Input field

huh.NewInput().
    Title("What's for lunch?").
    Prompt("?").
    Validate(isFood).
    Value(&lunch)

Text

Prompt the user for multiple lines of text.

Text field

huh.NewText().
    Title("Tell me a story.").
    Validate(checkForPlagiarism).
    Value(&story)

Select

Prompt the user to select from a list.

Select field

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)

Multiple Select

Prompt the user to select multiple options from a list.

Multiselect field

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)

Confirm

Prompt the user to confirm an option (yes or no).

Confirm field

huh.NewConfirm().
    Title("You sure?").
    Affirmative("Yes!").
    Negative("No.").
    Value(&confirm)

Accessibility

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.

Accessible cuisine form

Themes

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

Charm-themed form Dracula-themed form Base 16-themed form Default-themed form

Spinner

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.

Spinner while making a burger

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 Contexts. 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.

Bubbletea + Huh?

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.

Feedback

We'd love to hear your thoughts on this project. Feel free to drop us a note!

Acknowledgments

huh is inspired by the wonderful Survey library by Alec Aivazis.

License

MIT


Part of Charm.

The Charm logo

Charm热爱开源 • Charm loves open source • نحنُ نحب المصادر المفتوحة