Loading...
Loading...
Build interactive terminal forms and prompts in Go with huh - input, select, confirm, multiselect, validation, theming. Use when building Go terminal forms, huh, interactive Go prompts, or form fields with validation. NOT for shell script prompts (use gum).
npx skill4agent add alxxpersonal/forge charm-huhcharm.land/huh/v2package main
import (
"fmt"
"log"
"charm.land/huh/v2"
)
func main() {
var name string
var confirm bool
err := huh.NewForm(
huh.NewGroup(
huh.NewInput().
Title("What's your name?").
Value(&name).
Validate(huh.ValidateNotEmpty()),
huh.NewConfirm().
Title("Ready?").
Value(&confirm),
),
).Run()
if err != nil {
log.Fatal(err)
}
fmt.Printf("Hello, %s!\n", name)
}var name string
huh.NewInput().Title("Name?").Value(&name).Run()FormGroupFieldform := huh.NewForm(groups...*Group) *Form| Method | Purpose |
|---|---|
| Block and run the form |
| Run with context (supports cancellation) |
| Set theme |
| Set dimensions |
| Screen reader mode |
| Toggle help bar |
| Toggle error display |
| Auto-cancel after duration |
| Set group layout |
| Custom keybindings |
| Custom IO |
form.GetString("key")
form.GetInt("key")
form.GetBool("key")
form.Get("key") // anyhuh.StateNormalhuh.StateCompletedhuh.StateAbortedhuh.ErrUserAbortedhuh.ErrTimeoutgroup := huh.NewGroup(fields ...Field) *Group| Method | Purpose |
|---|---|
| Group header |
| Skip this group |
| Conditionally skip group |
| Toggle help for group |
.Title(s).Description(s).Key(s).Value(&v).Validate(fn).Run().TitleFunc(fn, binding).DescriptionFunc(fn, binding)stringhuh.NewInput().
Title("Email").
Placeholder("you@example.com").
Prompt("> ").
CharLimit(100).
Suggestions([]string{"gmail.com", "outlook.com"}).
EchoMode(huh.EchoModePassword). // or EchoModeNone
Inline(true). // title and input on same line
Validate(huh.ValidateNotEmpty()).
Value(&email)stringhuh.NewText().
Title("Description").
Lines(5).
CharLimit(500).
Placeholder("Enter details...").
ShowLineNumbers(true).
Editor("vim"). // external editor support (ctrl+e)
EditorExtension("md").
ExternalEditor(false). // disable external editor
Value(&description)Select[T comparable]huh.NewSelect[string]().
Title("Country").
Options(
huh.NewOption("United States", "US"),
huh.NewOption("Canada", "CA"),
).
Height(8). // scrollable if options exceed height
Inline(true). // horizontal left/right navigation
Filtering(true). // start with filter active
Value(&country)Options(huh.NewOptions("Warrior", "Mage", "Rogue")...)MultiSelect[T comparable]huh.NewMultiSelect[string]().
Title("Toppings").
Options(
huh.NewOption("Lettuce", "lettuce").Selected(true),
huh.NewOption("Tomato", "tomato"),
huh.NewOption("Cheese", "cheese"),
).
Limit(3).
Height(6).
Filterable(false). // disable "/" filter
Value(&toppings)a/boolhuh.NewConfirm().
Title("Continue?").
Affirmative("Yes!").
Negative("No way").
Inline(true).
Value(&ok)hlynhuh.NewNote().
Title("Welcome").
Description("This form collects your _preferences_.").
Height(10).
Next(true). // show a "Next" button, makes it interactive
NextLabel("Continue")_italic_*bold*`code`huh.NewForm(
huh.NewGroup(/* step 1 fields */).Title("Step 1"),
huh.NewGroup(/* step 2 fields */).Title("Step 2"),
huh.NewGroup(/* step 3 fields */).Title("Step 3"),
).Run()var wantExtras bool
huh.NewForm(
huh.NewGroup(
huh.NewConfirm().Title("Want extras?").Value(&wantExtras),
),
huh.NewGroup(
huh.NewInput().Title("Extra details").Value(&details),
).WithHideFunc(func() bool { return !wantExtras }),
).Run()*Funcvar country string
huh.NewSelect[string]().
Value(&state).
TitleFunc(func() string {
if country == "Canada" { return "Province" }
return "State"
}, &country).
OptionsFunc(func() []huh.Option[string] {
return huh.NewOptions(statesByCountry[country]...)
}, &country)&countryhuh.ValidateNotEmpty()
huh.ValidateMinLength(3)
huh.ValidateMaxLength(100)
huh.ValidateLength(3, 100) // min and max
huh.ValidateOneOf("a", "b", "c").Validate(func(s string) error {
if !strings.Contains(s, "@") {
return fmt.Errorf("must be a valid email")
}
return nil
})ThemeCharmThemeDraculaThemeCatppuccinThemeBase16ThemeDefaultform.WithTheme(huh.ThemeFunc(huh.ThemeDracula))Themetype Theme interface {
Theme(isDark bool) *Styles
}ThemeFuncform.WithTheme(huh.ThemeFunc(func(isDark bool) *huh.Styles {
s := huh.ThemeCharm(isDark) // start from a base
s.Focused.Title = lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("212"))
return s
}))form.WithLayout(huh.LayoutDefault) // one group at a time (default)
form.WithLayout(huh.LayoutStack) // all groups stacked vertically
form.WithLayout(huh.LayoutColumns(2)) // groups in 2 columns
form.WithLayout(huh.LayoutGrid(2, 3)) // 2 rows, 3 columnsaccessible := os.Getenv("ACCESSIBLE") != ""
form.WithAccessible(accessible)TERM=dumbimport "charm.land/huh/v2/spinner"
err := spinner.New().
Title("Processing...").
Action(func() { /* do work */ }).
Run()go doWork()
spinner.New().Title("Working...").Context(ctx).Run().Run()form.Run()
// or
huh.NewInput().Title("Name?").Value(&name).Run()*huh.Formtea.Modeltype Model struct {
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"),
),
),
}
}
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
}
if m.form.State == huh.StateCompleted {
return m, tea.Quit
}
return m, cmd
}
func (m Model) View() string {
if m.form.State == huh.StateCompleted {
return fmt.Sprintf("You picked: %s", m.form.GetString("class"))
}
return m.form.View()
}.Run()SubmitCmdCancelCmd.Key("name")form.GetString("name")form.Stateform.(*huh.Form)form.NextGroup()form.PrevGroup()form.NextField()form.PrevField()form.GetFocusedField().Value(&v)EmbeddedAccessor.Key()form.GetString().Run().Run()huh.NewSelect[string]()huh.NewSelect()TitleFunc(fn, country)TitleFunc(fn, &country)WithTimeout()ErrTimeoutUnsupportedform.Run()huh.ErrUserAborted.WithOutput(os.Stdout)charm.land/huh/v2.Value(&var).Key("name")nilerror*FuncErrUserAborted.Run().Run()form.State == huh.StateCompleted