• Stars
    star
    2,956
  • Rank 14,678 (Top 0.3 %)
  • Language
    Go
  • License
    MIT License
  • Created 7 months ago
  • Updated about 1 month ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Build terminal forms and prompts 🤷🏻‍♀️

Huh?

Hey there! I'm Glenn!

Latest Release Go Docs Build Status

A simple, powerful library for building interactive forms and prompts in the terminal.

Running a burger form

huh? is easy to use in a standalone fashion, can be integrated into a Bubble Tea application, and contains a first-class accessible mode for screen readers.

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

Tutorial

Let’s build a form for ordering burgers. To start, we’ll import the library and define a few variables where’ll we store answers.

package main

import "github.com/charmbracelet/huh"

var (
    burger string
    toppings []string
    sauceLevel int
    name string
    instructions string
    discount bool
)

huh? separates forms into groups (you can think of groups as pages). Groups are made of fields (e.g. Select, Input, Text). We will set up three groups for the customer to fill out.

form := huh.NewForm(
    huh.NewGroup(
        // Ask the user for a base burger and toppings.
        huh.NewSelect[string]().
            Title("Choose your burger").
            Options(
                huh.NewOption("Charmburger Classic", "classic"),
                huh.NewOption("Chickwich", "chickwich"),
                huh.NewOption("Fishburger", "fishburger"),
                huh.NewOption("Charmpossible™ Burger", "charmpossible"),
            ).
            Value(&burger), // store the chosen option in the "burger" variable

        // Let the user select multiple toppings.
        huh.NewMultiSelect[string]().
            Title("Toppings").
            Options(
                huh.NewOption("Lettuce", "lettuce").Selected(true),
                huh.NewOption("Tomatoes", "tomatoes").Selected(true),
                huh.NewOption("Jalapeños", "jalapeños"),
                huh.NewOption("Cheese", "cheese"),
                huh.NewOption("Vegan Cheese", "vegan cheese"),
                huh.NewOption("Nutella", "nutella"),
            ).
            Limit(4). // there’s a 4 topping limit!
            Value(&toppings),

        // Option values in selects and multi selects can be any type you
        // want. We’ve been recording strings above, but here we’ll store
        // answers as integers. Note the generic "[int]" directive below.
        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),
    ),

    // Gather some final details about the order.
    huh.NewGroup(
        huh.NewInput().
            Title("What's your name?").
            Value(&name).
            // Validating fields is easy. The form will mark erroneous fields
            // and display error messages accordingly.
            Validate(func(str string) error {
                if str == "Frank" {
                    return errors.New("Sorry, we don’t serve customers named Frank.")
                }
                return nil
            }),

        huh.NewText().
            Title("Special Instructions").
            CharLimit(400).
            Value(&instructions),

        huh.NewConfirm().
            Title("Would you like 15% off?").
            Value(&discount),
    ),
)

Finally, run the form:

err := form.Run()
if err != nil {
    log.Fatal(err)
}

if !discount {
    fmt.Println("What? You didn’t take the discount?!")
}

And that’s it! For more info see the full source for this example as well as the docs.

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)

Tip

Just want to prompt the user with a single field? Each field has a Run method that can be used as a shorthand for gathering quick and easy input.

var name string

huh.NewInput().
    Title("What's your name?").
    Value(&name).
    Run() // this is blocking...

fmt.Printf("Hey, %s!\n", name)

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 a single option 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 (zero or more) 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 (Yes or No).

Confirm field

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

Accessibility

huh? has a special rendering option designed specifically for screen readers. You can enable it with form.WithAccessible(true).

Tip

We recommend setting this through an environment variable or configuration option to allow the user to control accessibility.

accessibleMode := os.Getenv("ACCESSIBLE") != ""
form.WithAccessible(accessibleMode)

Accessible forms will drop TUIs in favor of standard prompts, providing better dictation and feedback of the information on screen for the visually impaired.

Accessible cuisine form

Themes

huh? contains a powerful theme abstraction. Supply your own custom theme or choose from one of the five predefined themes:

  • Charm
  • Dracula
  • Catppuccin
  • Base 16
  • Default

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

Themes can take advantage of the full range of Lip Gloss style options. For a high level theme reference see the docs.

Bonus: Spinner

huh? ships with a standalone spinner package. It’s useful for indicating background activity after a form is submitted.

Spinner while making a burger

Create a new spinner, set a title, set the action (or provide a Context), and run the spinner:

Action Style Context Style
err := spinner.New().
    Title("Making your burger...").
    Action(makeBurger).
    Run()

fmt.Println("Order up!")
go makeBurger()

err := spinner.New().
    Type(spinner.Line).
    Title("Making your burger...").
    Context(ctx).
    Run()

fmt.Println("Order up!")

For more on Spinners see the spinner examples and the spinner docs.

What about Bubble Tea?

Bubbletea + Huh?

In addition to its standalone mode, huh? has first-class support for Bubble Tea and can be easily integrated into Bubble Tea applications. It’s incredibly useful in portions of your Bubble Tea application that need form-like input.

Bubble Tea embedded form example

A huh.Form is merely a tea.Model, so you can use it just as you would any other Bubble.

type Model struct {
    form *huh.Form // huh.Form is just a tea.Model
}

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 {
        class := m.form.GetString("class")
        level := m.form.GetString("level")
        return fmt.Sprintf("You selected: %s, Lvl. %d", class, level)
    }
    return m.form.View()
}

For more info in using huh? in Bubble Tea applications see the full Bubble Tea example.

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 • نحنُ نحب المصادر المفتوحة

More Repositories

1

bubbletea

A powerful little TUI framework 🏗
Go
23,251
star
2

gum

A tool for glamorous shell scripts 🎀
Go
16,385
star
3

glow

Render markdown on the CLI, with pizzazz! 💅🏻
Go
14,499
star
4

vhs

Your CLI home video recorder 📼
Go
13,375
star
5

lipgloss

Style definitions for nice terminal layouts 👄
Go
7,073
star
6

soft-serve

The mighty, self-hostable Git server for the command line🍦
Go
4,638
star
7

bubbles

TUI components for Bubble Tea 🫧
Go
4,509
star
8

wish

Make SSH apps, just like that! 💫
Go
2,816
star
9

mods

AI on the command line
Go
2,261
star
10

charm

The Charm Tool and Library 🌟
Go
2,177
star
11

glamour

Stylesheet-based markdown rendering for your CLI apps 💇🏻‍♀️
Go
2,057
star
12

pop

Send emails from your terminal 📬
Go
2,044
star
13

log

A minimal, colorful Go logging library 🪵
Go
1,901
star
14

skate

A personal key value store 🛼
Go
1,167
star
15

wishlist

The SSH directory ✨
Go
959
star
16

harmonica

A simple, physics-based animation library 🎼
Go
945
star
17

melt

🧊 Backup and restore Ed25519 SSH keys with seed words.
Go
418
star
18

kancli

A tutorial for building a command line kanban board in Go
Go
155
star
19

vhs-action

Keep your GIFs up to date with VHS + GitHub actions 📽️
TypeScript
141
star
20

keygen

An SSH key pair generator 🗝️
Go
101
star
21

bubbletea-app-template

A template repository to create Bubbletea apps.
Go
99
star
22

inspo

Share and explore projects you can build with Charm libraries
87
star
23

taskcli

A tutorial for building a Taskwarrior-inspired task tracker in Go using glamorous CLI libraries
Go
77
star
24

wizard-tutorial

A basic wizard made with Bubble Tea and Lip Gloss. Follow along with the tutorial video for this project:
Go
73
star
25

tree-sitter-vhs

Syntax highlighting for VHS with tree-sitter 🌳
C
69
star
26

x

Charm experimental packages.
Go
65
star
27

confettysh

confetti over ssh
Go
49
star
28

catwalk

Open source 3D models from Charm 🧸
49
star
29

soft-serve-action

Synchronize GitHub repositories to your Soft Serve instance 🍦
43
star
30

git-lfs-transfer

Server-side implementation of the Git LFS pure-SSH protocol
Go
42
star
31

promwish

Prometheus middleware for Wish
Go
37
star
32

meta

Charm's meta configuration files 🫥
23
star
33

homebrew-tap

Our homebrew tap 🍺
Ruby
21
star
34

scoop-bucket

Charmbracelet Scoop Bucket
14
star
35

nur

Nix
13
star
36

.github

1
star