{"id":13491454,"url":"https://github.com/charmbracelet/huh","last_synced_at":"2025-05-12T13:19:10.572Z","repository":{"id":211915404,"uuid":"703663547","full_name":"charmbracelet/huh","owner":"charmbracelet","description":"Build terminal forms and prompts 🤷🏻‍♀️","archived":false,"fork":false,"pushed_at":"2025-05-09T12:44:03.000Z","size":1036,"stargazers_count":5483,"open_issues_count":40,"forks_count":168,"subscribers_count":16,"default_branch":"main","last_synced_at":"2025-05-12T13:18:58.617Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/charmbracelet.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-10-11T16:59:24.000Z","updated_at":"2025-05-12T07:38:22.000Z","dependencies_parsed_at":"2024-05-21T05:31:28.246Z","dependency_job_id":"3aaf5748-7819-4635-b1aa-7d2ab1cb53dd","html_url":"https://github.com/charmbracelet/huh","commit_stats":null,"previous_names":["charmbracelet/huh"],"tags_count":6,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/charmbracelet%2Fhuh","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/charmbracelet%2Fhuh/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/charmbracelet%2Fhuh/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/charmbracelet%2Fhuh/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/charmbracelet","download_url":"https://codeload.github.com/charmbracelet/huh/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253745196,"owners_count":21957319,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-07-31T19:00:57.179Z","updated_at":"2025-05-12T13:19:10.543Z","avatar_url":"https://github.com/charmbracelet.png","language":"Go","funding_links":[],"categories":["Go","Prompt \u0026 Input","others","Applications"],"sub_categories":["Development Tools"],"readme":"# Huh?\n\n\u003cp\u003e\n  \u003cimg alt=\"Hey there! I’m Glenn!\" title=\"Hey there! I’m Glenn!\" src=\"https://stuff.charm.sh/huh/glenn.png\" width=\"400\" /\u003e\n  \u003cbr\u003e\u003cbr\u003e\n  \u003ca href=\"https://github.com/charmbracelet/huh/releases\"\u003e\u003cimg src=\"https://img.shields.io/github/release/charmbracelet/huh.svg\" alt=\"Latest Release\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://pkg.go.dev/github.com/charmbracelet/huh?tab=doc\"\u003e\u003cimg src=\"https://godoc.org/github.com/golang/gddo?status.svg\" alt=\"Go Docs\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://github.com/charmbracelet/huh/actions\"\u003e\u003cimg src=\"https://github.com/charmbracelet/huh/actions/workflows/build.yml/badge.svg?branch=main\" alt=\"Build Status\"\u003e\u003c/a\u003e\n\u003c/p\u003e\n\nA simple, powerful library for building interactive forms and prompts in the terminal.\n\n\u003cimg alt=\"Running a burger form\" width=\"600\" src=\"https://vhs.charm.sh/vhs-3J4i6HE3yBmz6SUO3HqILr.gif\"\u003e\n\n`huh?` is easy to use in a standalone fashion, can be\n[integrated into a Bubble Tea application](#what-about-bubble-tea), and contains\na first-class [accessible mode](#accessibility) for screen readers.\n\nThe above example is running from a single Go program ([source](./examples/burger/main.go)).\n\n## Tutorial\n\nLet’s build a form for ordering burgers. To start, we’ll import the library and\ndefine a few variables where we'll store answers.\n\n```go\npackage main\n\nimport \"github.com/charmbracelet/huh\"\n\nvar (\n    burger       string\n    toppings     []string\n    sauceLevel   int\n    name         string\n    instructions string\n    discount     bool\n)\n```\n\n`huh?` separates forms into groups (you can think of groups as pages). Groups\nare made of fields (e.g. `Select`, `Input`, `Text`). We will set up three\ngroups for the customer to fill out.\n\n```go\nform := huh.NewForm(\n    huh.NewGroup(\n        // Ask the user for a base burger and toppings.\n        huh.NewSelect[string]().\n            Title(\"Choose your burger\").\n            Options(\n                huh.NewOption(\"Charmburger Classic\", \"classic\"),\n                huh.NewOption(\"Chickwich\", \"chickwich\"),\n                huh.NewOption(\"Fishburger\", \"fishburger\"),\n                huh.NewOption(\"Charmpossible™ Burger\", \"charmpossible\"),\n            ).\n            Value(\u0026burger), // store the chosen option in the \"burger\" variable\n\n        // Let the user select multiple toppings.\n        huh.NewMultiSelect[string]().\n            Title(\"Toppings\").\n            Options(\n                huh.NewOption(\"Lettuce\", \"lettuce\").Selected(true),\n                huh.NewOption(\"Tomatoes\", \"tomatoes\").Selected(true),\n                huh.NewOption(\"Jalapeños\", \"jalapeños\"),\n                huh.NewOption(\"Cheese\", \"cheese\"),\n                huh.NewOption(\"Vegan Cheese\", \"vegan cheese\"),\n                huh.NewOption(\"Nutella\", \"nutella\"),\n            ).\n            Limit(4). // there’s a 4 topping limit!\n            Value(\u0026toppings),\n\n        // Option values in selects and multi selects can be any type you\n        // want. We’ve been recording strings above, but here we’ll store\n        // answers as integers. Note the generic \"[int]\" directive below.\n        huh.NewSelect[int]().\n            Title(\"How much Charm Sauce do you want?\").\n            Options(\n                huh.NewOption(\"None\", 0),\n                huh.NewOption(\"A little\", 1),\n                huh.NewOption(\"A lot\", 2),\n            ).\n            Value(\u0026sauceLevel),\n    ),\n\n    // Gather some final details about the order.\n    huh.NewGroup(\n        huh.NewInput().\n            Title(\"What’s your name?\").\n            Value(\u0026name).\n            // Validating fields is easy. The form will mark erroneous fields\n            // and display error messages accordingly.\n            Validate(func(str string) error {\n                if str == \"Frank\" {\n                    return errors.New(\"Sorry, we don’t serve customers named Frank.\")\n                }\n                return nil\n            }),\n\n        huh.NewText().\n            Title(\"Special Instructions\").\n            CharLimit(400).\n            Value(\u0026instructions),\n\n        huh.NewConfirm().\n            Title(\"Would you like 15% off?\").\n            Value(\u0026discount),\n    ),\n)\n```\n\nFinally, run the form:\n\n```go\nerr := form.Run()\nif err != nil {\n    log.Fatal(err)\n}\n\nif !discount {\n    fmt.Println(\"What? You didn’t take the discount?!\")\n}\n```\n\nAnd that’s it! For more info see [the full source][burgersource] for this\nexample as well as [the docs][docs].\n\nIf you need more dynamic forms that change based on input from previous fields,\ncheck out the [dynamic forms](#dynamic-forms) example.\n\n[burgersource]: ./examples/burger/main.go\n[docs]: https://pkg.go.dev/github.com/charmbracelet/huh?tab=doc\n\n## Field Reference\n\n- [`Input`](#input): single line text input\n- [`Text`](#text): multi-line text input\n- [`Select`](#select): select an option from a list\n- [`MultiSelect`](#multiple-select): select multiple options from a list\n- [`Confirm`](#confirm): confirm an action (yes or no)\n\n\u003e [!TIP]\n\u003e Just want to prompt the user with a single field? Each field has a `Run`\n\u003e method that can be used as a shorthand for gathering quick and easy input.\n\n```go\nvar name string\n\nhuh.NewInput().\n    Title(\"What’s your name?\").\n    Value(\u0026name).\n    Run() // this is blocking...\n\nfmt.Printf(\"Hey, %s!\\n\", name)\n```\n\n### Input\n\nPrompt the user for a single line of text.\n\n\u003cimg alt=\"Input field\" width=\"600\" src=\"https://vhs.charm.sh/vhs-1ULe9JbTHfwFmm3hweRVtD.gif\"\u003e\n\n```go\nhuh.NewInput().\n    Title(\"What’s for lunch?\").\n    Prompt(\"?\").\n    Validate(isFood).\n    Value(\u0026lunch)\n```\n\n### Text\n\nPrompt the user for multiple lines of text.\n\n\u003cimg alt=\"Text field\" width=\"600\" src=\"https://vhs.charm.sh/vhs-2rrIuVSEf38bT0cwc8hfEG.gif\"\u003e\n\n```go\nhuh.NewText().\n    Title(\"Tell me a story.\").\n    Validate(checkForPlagiarism).\n    Value(\u0026story)\n```\n\n### Select\n\nPrompt the user to select a single option from a list.\n\n\u003cimg alt=\"Select field\" width=\"600\" src=\"https://vhs.charm.sh/vhs-7wFqZlxMWgbWmOIpBqXJTi.gif\"\u003e\n\n```go\nhuh.NewSelect[string]().\n    Title(\"Pick a country.\").\n    Options(\n        huh.NewOption(\"United States\", \"US\"),\n        huh.NewOption(\"Germany\", \"DE\"),\n        huh.NewOption(\"Brazil\", \"BR\"),\n        huh.NewOption(\"Canada\", \"CA\"),\n    ).\n    Value(\u0026country)\n```\n\n### Multiple Select\n\nPrompt the user to select multiple (zero or more) options from a list.\n\n\u003cimg alt=\"Multiselect field\" width=\"600\" src=\"https://vhs.charm.sh/vhs-3TLImcoexOehRNLELysMpK.gif\"\u003e\n\n```go\nhuh.NewMultiSelect[string]().\n    Options(\n        huh.NewOption(\"Lettuce\", \"Lettuce\").Selected(true),\n        huh.NewOption(\"Tomatoes\", \"Tomatoes\").Selected(true),\n        huh.NewOption(\"Charm Sauce\", \"Charm Sauce\"),\n        huh.NewOption(\"Jalapeños\", \"Jalapeños\"),\n        huh.NewOption(\"Cheese\", \"Cheese\"),\n        huh.NewOption(\"Vegan Cheese\", \"Vegan Cheese\"),\n        huh.NewOption(\"Nutella\", \"Nutella\"),\n    ).\n    Title(\"Toppings\").\n    Limit(4).\n    Value(\u0026toppings)\n```\n\n### Confirm\n\nPrompt the user to confirm (Yes or No).\n\n\u003cimg alt=\"Confirm field\" width=\"600\" src=\"https://vhs.charm.sh/vhs-2HeX5MdOxLsrWwsa0TNMIL.gif\"\u003e\n\n```go\nhuh.NewConfirm().\n    Title(\"Are you sure?\").\n    Affirmative(\"Yes!\").\n    Negative(\"No.\").\n    Value(\u0026confirm)\n```\n\n## Accessibility\n\n`huh?` has a special rendering option designed specifically for screen readers.\nYou can enable it with `form.WithAccessible(true)`.\n\n\u003e [!TIP]\n\u003e We recommend setting this through an environment variable or configuration\n\u003e option to allow the user to control accessibility.\n\n```go\naccessibleMode := os.Getenv(\"ACCESSIBLE\") != \"\"\nform.WithAccessible(accessibleMode)\n```\n\nAccessible forms will drop TUIs in favor of standard prompts, providing better\ndictation and feedback of the information on screen for the visually impaired.\n\n\u003cimg alt=\"Accessible cuisine form\" width=\"600\" src=\"https://vhs.charm.sh/vhs-19xEBn4LgzPZDtgzXRRJYS.gif\"\u003e\n\n## Themes\n\n`huh?` contains a powerful theme abstraction. Supply your own custom theme or\nchoose from one of the five predefined themes:\n\n- `Charm`\n- `Dracula`\n- `Catppuccin`\n- `Base 16`\n- `Default`\n\n\u003cbr /\u003e\n\u003cp\u003e\n    \u003cimg alt=\"Charm-themed form\" width=\"400\" src=\"https://stuff.charm.sh/huh/themes/charm-theme.png\"\u003e\n    \u003cimg alt=\"Dracula-themed form\" width=\"400\" src=\"https://stuff.charm.sh/huh/themes/dracula-theme.png\"\u003e\n    \u003cimg alt=\"Catppuccin-themed form\" width=\"400\" src=\"https://stuff.charm.sh/huh/themes/catppuccin-theme.png\"\u003e\n    \u003cimg alt=\"Base 16-themed form\" width=\"400\" src=\"https://stuff.charm.sh/huh/themes/basesixteen-theme.png\"\u003e\n    \u003cimg alt=\"Default-themed form\" width=\"400\" src=\"https://stuff.charm.sh/huh/themes/default-theme.png\"\u003e\n\u003c/p\u003e\n\nThemes can take advantage of the full range of\n[Lip Gloss][lipgloss] style options. For a high level theme reference see\n[the docs](https://pkg.go.dev/github.com/charmbracelet/huh#Theme).\n\n[lipgloss]: https://github.com/charmbracelet/lipgloss\n\n## Dynamic Forms\n\n`huh?` forms can be as dynamic as your heart desires. Simply replace properties\nwith their equivalent `Func` to recompute the properties value every time a\ndifferent part of your form changes.\n\nHere’s how you would build a simple country + state / province picker.\n\nFirst, define some variables that we’ll use to store the user selection.\n\n```go\nvar country string\nvar state string\n```\n\nDefine your country select as you normally would:\n\n```go\nhuh.NewSelect[string]().\n    Options(huh.NewOptions(\"United States\", \"Canada\", \"Mexico\")...).\n    Value(\u0026country).\n    Title(\"Country\").\n```\n\nDefine your state select with `TitleFunc` and `OptionsFunc` instead of `Title`\nand `Options`. This will allow you to change the title and options based on the\nselection of the previous field, i.e. `country`.\n\nTo do this, we provide a `func() string` and a `binding any` to `TitleFunc`. The\nfunction defines what to show for the title and the binding specifies what value\nneeds to change for the function to recompute. So if `country` changes (e.g. the\nuser changes the selection) we will recompute the function.\n\nFor `OptionsFunc`, we provide a `func() []Option[string]` and a `binding any`.\nWe’ll fetch the country’s states, provinces, or territories from an API. `huh`\nwill automatically handle caching for you.\n\n\u003e [!IMPORTANT]\n\u003e We have to pass `\u0026country` as the binding to recompute the function only when\n\u003e `country` changes, otherwise we will hit the API too often.\n\n```go\nhuh.NewSelect[string]().\n    Value(\u0026state).\n    Height(8).\n    TitleFunc(func() string {\n        switch country {\n        case \"United States\":\n            return \"State\"\n        case \"Canada\":\n            return \"Province\"\n        default:\n            return \"Territory\"\n        }\n    }, \u0026country).\n    OptionsFunc(func() []huh.Option[string] {\n        opts := fetchStatesForCountry(country)\n        return huh.NewOptions(opts...)\n    }, \u0026country),\n```\n\nLastly, run the `form` with these inputs.\n\n```go\nerr := form.Run()\nif err != nil {\n    log.Fatal(err)\n}\n```\n\n\u003cimg width=\"600\" src=\"https://vhs.charm.sh/vhs-6FRmBjNi2aiRb4INPXwIjo.gif\" alt=\"Country / State form with dynamic inputs running.\"\u003e\n\n## Bonus: Spinner\n\n`huh?` ships with a standalone spinner package. It’s useful for indicating\nbackground activity after a form is submitted.\n\n\u003cimg alt=\"Spinner while making a burger\" width=\"600\" src=\"https://vhs.charm.sh/vhs-6HvYomAFP6H8mngOYWXvwJ.gif\"\u003e\n\nCreate a new spinner, set a title, set the action (or provide a `Context`), and run the spinner:\n\n\u003ctable\u003e\n\n\u003ctr\u003e\n\u003ctd\u003e \u003cstrong\u003eAction Style\u003c/strong\u003e \u003c/td\u003e\u003ctd\u003e \u003cstrong\u003eContext Style\u003c/strong\u003e \u003c/td\u003e\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\n\n```go\nerr := spinner.New().\n    Title(\"Making your burger...\").\n    Action(makeBurger).\n    Run()\n\nfmt.Println(\"Order up!\")\n```\n\n\u003c/td\u003e\n\u003ctd\u003e\n\n```go\ngo makeBurger()\n\nerr := spinner.New().\n    Type(spinner.Line).\n    Title(\"Making your burger...\").\n    Context(ctx).\n    Run()\n\nfmt.Println(\"Order up!\")\n```\n\n\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/table\u003e\n\nFor more on Spinners see the [spinner examples](./spinner/examples) and\n[the spinner docs](https://pkg.go.dev/github.com/charmbracelet/huh/spinner).\n\n## What about Bubble Tea?\n\n\u003cimg alt=\"Bubbletea + Huh?\" width=\"174\" src=\"https://stuff.charm.sh/huh/bubbletea-huh.png\"\u003e\n\nHuh is built on [Bubble Tea][tea] and, in addition to its standalone mode, \n`huh?` has first-class support and can be easily integrated into \nBubble Tea applications. It’s very useful in portions of your Bubble Tea \napplication that need form-like input, and for times when you need more\nflexibility than `huh?` alone can offer.\n\n\u003cimg alt=\"Bubble Tea embedded form example\" width=\"800\" src=\"https://vhs.charm.sh/vhs-3wGaB7EUKWmojeaHpARMUv.gif\"\u003e\n\nA `huh.Form` is just a `tea.Model`, so you can use it just as\nyou would any other [Bubble](https://github.com/charmbracelet/bubbles).\n\n```go\ntype Model struct {\n    form *huh.Form // huh.Form is just a tea.Model\n}\n\nfunc NewModel() Model {\n    return Model{\n        form: huh.NewForm(\n            huh.NewGroup(\n                huh.NewSelect[string]().\n                    Key(\"class\").\n                    Options(huh.NewOptions(\"Warrior\", \"Mage\", \"Rogue\")...).\n                    Title(\"Choose your class\"),\n\n            huh.NewSelect[int]().\n                Key(\"level\").\n                Options(huh.NewOptions(1, 20, 9999)...).\n                Title(\"Choose your level\"),\n            ),\n        )\n    }\n}\n\nfunc (m Model) Init() tea.Cmd {\n    return m.form.Init()\n}\n\nfunc (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {\n    // ...\n\n    form, cmd := m.form.Update(msg)\n    if f, ok := form.(*huh.Form); ok {\n        m.form = f\n    }\n\n    return m, cmd\n}\n\nfunc (m Model) View() string {\n    if m.form.State == huh.StateCompleted {\n        class := m.form.GetString(\"class\")\n        level := m.form.GetInt(\"level\")\n        return fmt.Sprintf(\"You selected: %s, Lvl. %d\", class, level)\n    }\n    return m.form.View()\n}\n\n```\n\nFor more info in using `huh?` in Bubble Tea applications see [the full Bubble\nTea example][example].\n\n[tea]: https://github.com/charmbracelet/bubbletea\n[bubbles]: https://github.com/charmbracelet/bubbles\n[example]: https://github.com/charmbracelet/huh/blob/main/examples/bubbletea/main.go\n\n## `Huh?` in the Wild\n\nFor some `Huh?` programs in production, see:\n\n* [glyphs](https://github.com/maaslalani/glyphs): a unicode symbol picker\n* [meteor](https://github.com/stefanlogue/meteor): a highly customisable conventional commit message tool\n* [freeze](https://github.com/charmbracelet/freeze): a tool for generating images of code and terminal output\n* [savvy](https://github.com/getsavvyinc/savvy-cli): the easiest way to create, share, and run runbooks in the terminal\n\n## Contributing\n\nSee [contributing][contribute].\n\n[contribute]: https://github.com/charmbracelet/huh/contribute\n\n## Feedback\n\nWe’d love to hear your thoughts on this project. Feel free to drop us a note!\n\n- [Twitter](https://twitter.com/charmcli)\n- [The Fediverse](https://mastodon.social/@charmcli)\n- [Discord](https://charm.sh/chat)\n\n## Acknowledgments\n\n`huh?` is inspired by the wonderful [Survey][survey] library by Alec Aivazis.\n\n[survey]: https://github.com/AlecAivazis/survey\n\n## License\n\n[MIT](https://github.com/charmbracelet/bubbletea/raw/master/LICENSE)\n\n---\n\nPart of [Charm](https://charm.sh).\n\n\u003ca href=\"https://charm.sh/\"\u003e\u003cimg alt=\"The Charm logo\" src=\"https://stuff.charm.sh/charm-badge.jpg\" width=\"400\"\u003e\u003c/a\u003e\n\nCharm热爱开源 • Charm loves open source • نحنُ نحب المصادر المفتوحة\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcharmbracelet%2Fhuh","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcharmbracelet%2Fhuh","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcharmbracelet%2Fhuh/lists"}