[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-71454":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":10,"language":11,"languages":10,"totalLinesOfCode":10,"stars":12,"forks":13,"watchers":14,"openIssues":15,"contributorsCount":16,"subscribersCount":16,"size":16,"stars1d":17,"stars7d":14,"stars30d":18,"stars90d":16,"forks30d":16,"starsTrendScore":19,"compositeScore":20,"rankGlobal":10,"rankLanguage":10,"license":21,"archived":22,"fork":22,"defaultBranch":23,"hasWiki":24,"hasPages":22,"topics":25,"createdAt":10,"pushedAt":10,"updatedAt":26,"readmeContent":27,"aiSummary":28,"trendingCount":16,"starSnapshotCount":16,"syncStatus":29,"lastSyncTime":30,"discoverSource":31},71454,"huh","charmbracelet\u002Fhuh","charmbracelet","Build terminal forms and prompts 🤷🏻‍♀️","",null,"Go",6958,247,25,46,0,12,88,36,104.48,"MIT License",false,"main",true,[],"2026-06-12 04:01:01","# Huh?\n\n\u003Cp>\n  \u003Cimg src=\"https:\u002F\u002Fstuff.charm.sh\u002Fhuh\u002Fglenn.png\" width=\"400\" \u002F>\n  \u003Cbr>\u003Cbr>\n  \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fcharmbracelet\u002Fhuh\u002Freleases\">\u003Cimg src=\"https:\u002F\u002Fimg.shields.io\u002Fgithub\u002Frelease\u002Fcharmbracelet\u002Fhuh.svg\" alt=\"Latest Release\">\u003C\u002Fa>\n  \u003Ca href=\"https:\u002F\u002Fpkg.go.dev\u002Fcharm.land\u002Fhuh\u002Fv2#section-documentation\">\u003Cimg src=\"https:\u002F\u002Fgodoc.org\u002Fgithub.com\u002Fgolang\u002Fgddo?status.svg\" alt=\"Go Docs\">\u003C\u002Fa>\n  \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fcharmbracelet\u002Fhuh\u002Factions\">\u003Cimg src=\"https:\u002F\u002Fgithub.com\u002Fcharmbracelet\u002Fhuh\u002Factions\u002Fworkflows\u002Fbuild.yml\u002Fbadge.svg?branch=main\" alt=\"Build Status\">\u003C\u002Fa>\n\u003C\u002Fp>\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:\u002F\u002Fvhs.charm.sh\u002Fvhs-3J4i6HE3yBmz6SUO3HqILr.gif\">\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](.\u002Fexamples\u002Fburger\u002Fmain.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 \"charm.land\u002Fhuh\u002Fv2\"\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        \u002F\u002F 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(&burger), \u002F\u002F store the chosen option in the \"burger\" variable\n\n        \u002F\u002F 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). \u002F\u002F there’s a 4 topping limit!\n            Value(&toppings),\n\n        \u002F\u002F Option values in selects and multi selects can be any type you\n        \u002F\u002F want. We’ve been recording strings above, but here we’ll store\n        \u002F\u002F 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(&sauceLevel),\n    ),\n\n    \u002F\u002F Gather some final details about the order.\n    huh.NewGroup(\n        huh.NewInput().\n            Title(\"What’s your name?\").\n            Value(&name).\n            \u002F\u002F Validating fields is easy. The form will mark erroneous fields\n            \u002F\u002F 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(&instructions),\n\n        huh.NewConfirm().\n            Title(\"Would you like 15% off?\").\n            Value(&discount),\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]: .\u002Fexamples\u002Fburger\u002Fmain.go\n[docs]: https:\u002F\u002Fpkg.go.dev\u002Fcharm.land\u002Fhuh\u002Fv2?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> [!TIP]\n> Just want to prompt the user with a single field? Each field has a `Run`\n> 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(&name).\n    Run() \u002F\u002F 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:\u002F\u002Fvhs.charm.sh\u002Fvhs-1ULe9JbTHfwFmm3hweRVtD.gif\">\n\n```go\nhuh.NewInput().\n    Title(\"What’s for lunch?\").\n    Prompt(\"?\").\n    Validate(isFood).\n    Value(&lunch)\n```\n\n### Text\n\nPrompt the user for multiple lines of text.\n\n\u003Cimg alt=\"Text field\" width=\"600\" src=\"https:\u002F\u002Fvhs.charm.sh\u002Fvhs-2rrIuVSEf38bT0cwc8hfEG.gif\">\n\n```go\nhuh.NewText().\n    Title(\"Tell me a story.\").\n    Validate(checkForPlagiarism).\n    Value(&story)\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:\u002F\u002Fvhs.charm.sh\u002Fvhs-7wFqZlxMWgbWmOIpBqXJTi.gif\">\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(&country)\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:\u002F\u002Fvhs.charm.sh\u002Fvhs-3TLImcoexOehRNLELysMpK.gif\">\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(&toppings)\n```\n\n### Confirm\n\nPrompt the user to confirm (Yes or No).\n\n\u003Cimg alt=\"Confirm field\" width=\"600\" src=\"https:\u002F\u002Fvhs.charm.sh\u002Fvhs-2HeX5MdOxLsrWwsa0TNMIL.gif\">\n\n```go\nhuh.NewConfirm().\n    Title(\"Are you sure?\").\n    Affirmative(\"Yes!\").\n    Negative(\"No.\").\n    Value(&confirm)\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> [!TIP]\n> We recommend setting this through an environment variable or configuration\n> 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:\u002F\u002Fvhs.charm.sh\u002Fvhs-19xEBn4LgzPZDtgzXRRJYS.gif\">\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 \u002F>\n\u003Cp>\n    \u003Cimg alt=\"Charm-themed form\" width=\"400\" src=\"https:\u002F\u002Fstuff.charm.sh\u002Fhuh\u002Fthemes\u002Fcharm-theme.png\">\n    \u003Cimg alt=\"Dracula-themed form\" width=\"400\" src=\"https:\u002F\u002Fstuff.charm.sh\u002Fhuh\u002Fthemes\u002Fdracula-theme.png\">\n    \u003Cimg alt=\"Catppuccin-themed form\" width=\"400\" src=\"https:\u002F\u002Fstuff.charm.sh\u002Fhuh\u002Fthemes\u002Fcatppuccin-theme.png\">\n    \u003Cimg alt=\"Base 16-themed form\" width=\"400\" src=\"https:\u002F\u002Fstuff.charm.sh\u002Fhuh\u002Fthemes\u002Fbasesixteen-theme.png\">\n    \u003Cimg alt=\"Default-themed form\" width=\"400\" src=\"https:\u002F\u002Fstuff.charm.sh\u002Fhuh\u002Fthemes\u002Fdefault-theme.png\">\n\u003C\u002Fp>\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:\u002F\u002Fpkg.go.dev\u002Fcharm.land\u002Fhuh\u002Fv2#Theme).\n\n[lipgloss]: https:\u002F\u002Fgithub.com\u002Fcharmbracelet\u002Flipgloss\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 \u002F 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(&country).\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> [!IMPORTANT]\n> We have to pass `&country` as the binding to recompute the function only when\n> `country` changes, otherwise we will hit the API too often.\n\n```go\nhuh.NewSelect[string]().\n    Value(&state).\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    }, &country).\n    OptionsFunc(func() []huh.Option[string] {\n        opts := fetchStatesForCountry(country)\n        return huh.NewOptions(opts...)\n    }, &country),\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:\u002F\u002Fvhs.charm.sh\u002Fvhs-6FRmBjNi2aiRb4INPXwIjo.gif\" alt=\"Country \u002F State form with dynamic inputs running.\">\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:\u002F\u002Fvhs.charm.sh\u002Fvhs-6HvYomAFP6H8mngOYWXvwJ.gif\">\n\nCreate a new spinner, set a title, set the action (or provide a `Context`), and run the spinner:\n\n\u003Ctable>\n\n\u003Ctr>\n\u003Ctd> \u003Cstrong>Action Style\u003C\u002Fstrong> \u003C\u002Ftd>\u003Ctd> \u003Cstrong>Context Style\u003C\u002Fstrong> \u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\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\u002Ftd>\n\u003Ctd>\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\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftable>\n\nFor more on Spinners see the [spinner examples](.\u002Fspinner\u002Fexamples) and\n[the spinner docs](https:\u002F\u002Fpkg.go.dev\u002Fcharm.land\u002Fhuh\u002Fv2\u002Fspinner).\n\n## What about Bubble Tea?\n\n\u003Cimg alt=\"Bubbletea + Huh?\" width=\"174\" src=\"https:\u002F\u002Fstuff.charm.sh\u002Fhuh\u002Fbubbletea-huh.png\">\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:\u002F\u002Fvhs.charm.sh\u002Fvhs-3wGaB7EUKWmojeaHpARMUv.gif\">\n\nA `huh.Form` is just a `tea.Model`, so you can use it just as\nyou would any other [Bubble](https:\u002F\u002Fgithub.com\u002Fcharmbracelet\u002Fbubbles).\n\n```go\ntype Model struct {\n    form *huh.Form \u002F\u002F 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    \u002F\u002F ...\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:\u002F\u002Fgithub.com\u002Fcharmbracelet\u002Fbubbletea\n[bubbles]: https:\u002F\u002Fgithub.com\u002Fcharmbracelet\u002Fbubbles\n[example]: https:\u002F\u002Fgithub.com\u002Fcharmbracelet\u002Fhuh\u002Fblob\u002Fmain\u002Fexamples\u002Fbubbletea\u002Fmain.go\n\n## `Huh?` in the Wild\n\nFor some `Huh?` programs in production, see:\n\n* [glyphs](https:\u002F\u002Fgithub.com\u002Fmaaslalani\u002Fglyphs): a unicode symbol picker\n* [meteor](https:\u002F\u002Fgithub.com\u002Fstefanlogue\u002Fmeteor): a highly customisable conventional commit message tool\n* [freeze](https:\u002F\u002Fgithub.com\u002Fcharmbracelet\u002Ffreeze): a tool for generating images of code and terminal output\n* [savvy](https:\u002F\u002Fgithub.com\u002Fgetsavvyinc\u002Fsavvy-cli): the easiest way to create, share, and run runbooks in the terminal\n\n## Contributing\n\nSee [contributing][contribute].\n\n[contribute]: https:\u002F\u002Fgithub.com\u002Fcharmbracelet\u002Fhuh\u002Fcontribute\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:\u002F\u002Ftwitter.com\u002Fcharmcli)\n- [The Fediverse](https:\u002F\u002Fmastodon.social\u002F@charmcli)\n- [Discord](https:\u002F\u002Fcharm.sh\u002Fchat)\n\n## Acknowledgments\n\n`huh?` is inspired by the wonderful [Survey][survey] library by Alec Aivazis.\n\n[survey]: https:\u002F\u002Fgithub.com\u002FAlecAivazis\u002Fsurvey\n\n## License\n\n[MIT](https:\u002F\u002Fgithub.com\u002Fcharmbracelet\u002Fbubbletea\u002Fraw\u002Fmaster\u002FLICENSE)\n\n---\n\nPart of [Charm](https:\u002F\u002Fcharm.sh).\n\n\u003Ca href=\"https:\u002F\u002Fcharm.land\u002F\">\u003Cimg alt=\"The Charm logo\" src=\"https:\u002F\u002Fstuff.charm.sh\u002Fcharm-banner-next.jpg\" width=\"400\">\u003C\u002Fa>\n\nCharm热爱开源 • Charm loves open source • نحنُ نحب المصادر المفتوحة\n","huh? 是一个用于在终端构建交互式表单和提示的强大库。它支持创建选择题、多选题以及文本输入等多种字段类型，并且可以将这些字段组织成不同的组，便于用户按步骤填写信息。该项目采用Go语言编写，具有良好的可扩展性和易用性，同时提供了对屏幕阅读器的支持以增强可访问性。适合需要通过命令行界面收集用户输入的各种场景，如配置向导、数据采集脚本等。",2,"2026-06-11 03:37:48","high_star"]