https://github.com/marcoroth/huh-ruby
Build Beautiful Terminal forms and prompts in Ruby, based on Charm's Huh.
https://github.com/marcoroth/huh-ruby
Last synced: 15 days ago
JSON representation
Build Beautiful Terminal forms and prompts in Ruby, based on Charm's Huh.
- Host: GitHub
- URL: https://github.com/marcoroth/huh-ruby
- Owner: marcoroth
- License: mit
- Created: 2025-12-29T09:41:32.000Z (7 months ago)
- Default Branch: main
- Last Pushed: 2025-12-30T14:15:39.000Z (7 months ago)
- Last Synced: 2026-05-30T02:26:41.051Z (2 months ago)
- Language: Ruby
- Homepage: https://charm-ruby.dev
- Size: 86.9 KB
- Stars: 47
- Watchers: 1
- Forks: 3
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- License: LICENSE.txt
- Code of conduct: CODE_OF_CONDUCT.md
Awesome Lists containing this project
README
Huh? for Ruby
Build Beautiful Terminal forms and prompts in Ruby
Ruby port of charmbracelet/huh.
A simple, powerful library for building interactive forms and prompts in the terminal.
## Installation
**Add to your Gemfile:**
```ruby
gem "huh", github: "marcoroth/huh-ruby"
```
## Usage
### Basic Form
**Create a simple form with text inputs:**
```ruby
require "huh"
form = Huh.form(
Huh.group(
Huh.input
.key("name")
.title("What's your name?")
.placeholder("Enter your name..."),
Huh.input
.key("email")
.title("Email address")
.placeholder("you@example.com")
)
)
form.run
puts "Hello, #{form["name"]}!"
puts "Email: #{form["email"]}"
```
### Fields
#### Input
**Single-line text input:**
```ruby
input = Huh.input
.key("username")
.title("Username")
.placeholder("Enter username...")
.char_limit(20)
.validate(Huh::Validation.not_empty)
```
**Password input:**
```ruby
password = Huh.input
.key("password")
.title("Password")
.echo_mode(:password)
```
#### Text
**Multi-line text area:**
```ruby
text = Huh.text
.key("bio")
.title("Tell us about yourself")
.placeholder("Write something...")
.lines(5)
.char_limit(500)
```
#### Select
**Single selection from options:**
```ruby
select = Huh.select
.key("color")
.title("Pick a color")
.options(
Huh.option("Red", "#ff0000"),
Huh.option("Green", "#00ff00"),
Huh.option("Blue", "#0000ff")
)
```
**From simple values:**
```ruby
select = Huh.select
.key("fruit")
.title("Pick a fruit")
.options(*Huh.options("Apple", "Banana", "Cherry"))
```
#### MultiSelect
**Multiple selections:**
```ruby
multi = Huh.multi_select
.key("toppings")
.title("Choose toppings")
.options(*Huh.options("Cheese", "Pepperoni", "Mushrooms", "Olives"))
.limit(3)
```
#### Confirm
**Yes/No confirmation:**
```ruby
confirm = Huh.confirm
.key("agree")
.title("Do you agree to the terms?")
.affirmative("I agree")
.negative("I disagree")
```
#### Note
**Display-only information:**
```ruby
note = Huh.note
.title("Welcome!")
.description("This wizard will help you set up your project.")
.next(true)
.next_label("Get Started")
```
#### Spinner
**Loading indicator with background action:**
```ruby
spinner = Huh.spinner
.title("Loading data...")
.type(:dots)
.action { fetch_from_api }
error = spinner.run
puts "Failed: #{error.message}" if error
```
**Available spinner types:**
| Type | Animation |
|--------------|--------------------------------------|
| `:line` | `\|` `/` `-` `\` |
| `:dots` | `⣾` `⣽` `⣻` `⢿` `⡿` `⣟` `⣯` `⣷` |
| `:mini_dot` | `⠋` `⠙` `⠹` `⠸` ... |
| `:moon` | 🌑 🌒 🌓 🌔 🌕 🌖 🌗 🌘 |
| `:globe` | 🌍 🌎 🌏 |
| `:monkey` | 🙈 🙉 🙊 |
| `:meter` | `▱▱▱` `▰▱▱` `▰▰▱` `▰▰▰` |
| `:pulse` | `█` `▓` `▒` `░` |
| `:points` | `∙∙∙` `●∙∙` `∙●∙` `∙∙●` |
| `:hamburger` | `☱` `☲` `☴` |
| `:ellipsis` | `.` `..` `...` |
### Themes
**Apply built-in themes:**
```ruby
form = Huh.form(
Huh.group(
Huh.input.key("name").title("Name")
)
).with_theme(Huh::Themes.charm)
```
**Available themes:**
| Theme | Description |
|---------------------|--------------------|
| `Themes.base` | Minimal styling |
| `Themes.charm` | Indigo and fuchsia |
| `Themes.dracula` | Dark purple |
| `Themes.catppuccin` | Pastel colors |
| `Themes.base16` | Terminal colors |
### Validation
#### Built-in validators
**Required field**
```ruby
input.validate(Huh::Validation.not_empty)
```
**Minimum length**
```ruby
input.validate(Huh::Validation.min_length(3))
```
**Maximum length**
```ruby
input.validate(Huh::Validation.max_length(100))
```
**Length range**
```ruby
input.validate(Huh::Validation.length_range(3, 20))
```
**Email format**
```ruby
input.validate(Huh::Validation.email)
```
**Integer**
```ruby
input.validate(Huh::Validation.integer)
```
**URL**
```ruby
input.validate(Huh::Validation.url)
```
**Custom regex**
```ruby
input.validate(Huh::Validation.match(/^[a-z]+$/, "must be lowercase letters"))
```
**Custom validation:**
```ruby
input.validate do |value|
raise Huh::ValidationError, "must start with @" unless value.start_with?("@")
end
```
**Combine validators:**
```ruby
input.validate(
Huh::Validation.all(
Huh::Validation.not_empty,
Huh::Validation.min_length(3),
Huh::Validation.max_length(20)
)
)
```
**Block navigation on validation errors:**
```ruby
form = Huh.form(
Huh.group(
Huh.input.key("email").validate(Huh::Validation.email)
)
).with_validate_on_submit(true) # Users can't proceed until field is valid
```
### Dynamic Content
**Dynamic titles and descriptions:**
```ruby
count = 0
input = Huh.input
.key("item")
.title_func { "Item #{count + 1}" }
.description_func { "You've added #{count} items" }
```
**Dynamic options:**
```ruby
items = ["A", "B", "C"]
select = Huh.select
.key("item")
.title("Pick an item")
.options_func { Huh.options(*items) }
```
### Value Binding
**Bind to external variables:**
```ruby
user = Struct.new(:name, :email).new("", "")
form = Huh.form(
Huh.group(
Huh.input.key("name").title("Name").value(user, :name),
Huh.input.key("email").title("Email").value(user, :email)
)
)
form.run
puts user.name # Value is automatically synced
puts user.email
```
### Accessing Results
**Hash-style access:**
```ruby
errors = form.run
if errors.any?
errors.each { |e| puts "Error: #{e.message}" }
else
form["name"] # Get by key
form.get("name") # Same as above
form.to_h # Get all results as hash
end
```
### Groups
**Organize fields into groups:**
```ruby
form = Huh.form(
Huh.group(
Huh.input.key("name").title("Name"),
Huh.input.key("email").title("Email")
).title("Personal Info").description("Enter your details"),
Huh.group(
Huh.input.key("company").title("Company"),
Huh.input.key("role").title("Role")
).title("Work Info")
)
```
### Layouts
**Stack layout (default):**
```ruby
form.with_layout(Huh::Layout.stack)
```
**Column layout:**
```ruby
form.with_layout(Huh::Layout.columns(2))
```
### Standalone Fields
**Run a single field without a form:**
```ruby
name = Huh.input
.title("What's your name?")
.run
puts "Hello, #{name}!"
```
**Or use the convenience method:**
```ruby
result = Huh.run(
Huh.confirm.title("Continue?")
)
```
## Field Methods
### Common Methods
| Method | Description |
|--------------------------|---------------------------|
| `.key(string)` | Set result key |
| `.title(string)` | Set title text |
| `.title_func { }` | Set dynamic title |
| `.description(string)` | Set description text |
| `.description_func { }` | Set dynamic description |
| `.value(accessor)` | Bind to value accessor |
| `.value(object, :attr)` | Bind to object attribute |
| `.validate(validator)` | Set validation |
| `.validate { \|v\| }` | Set validation block |
| `.with_theme(theme)` | Apply theme |
| `.run` | Run field standalone |
### Input Methods
| Method | Description |
|-----------------------|-------------------------------------------------|
| `.placeholder(text)` | Set placeholder text |
| `.char_limit(int)` | Set character limit |
| `.echo_mode(mode)` | Set echo mode (`:normal`, `:password`, `:none`) |
| `.prompt(text)` | Set prompt character |
| `.suggestions(array)` | Set autocomplete suggestions |
| `.inline(bool)` | Enable inline mode |
### Text Methods
| Method | Description |
|----------------------------|----------------------|
| `.placeholder(text)` | Set placeholder text |
| `.char_limit(int)` | Set character limit |
| `.lines(int)` | Set visible lines |
| `.show_line_numbers(bool)` | Show line numbers |
### Select/MultiSelect Methods
| Method | Description |
|---------------------|--------------------------------|
| `.options(*opts)` | Set available options |
| `.options_func { }` | Set dynamic options |
| `.height(int)` | Set visible height |
| `.filtering(bool)` | Enable filtering |
| `.limit(int)` | Max selections (MultiSelect) |
### Confirm Methods
| Method | Description |
|----------------------|-----------------------|
| `.affirmative(text)` | Set "yes" button text |
| `.negative(text)` | Set "no" button text |
| `.inline(bool)` | Enable inline mode |
### Note Methods
| Method | Description |
|---------------------|----------------------|
| `.next(bool)` | Show next button |
| `.next_label(text)` | Set next button text |
### Spinner Methods
| Method | Description |
|--------------------------|-----------------------|
| `.title(text)` | Set spinner title |
| `.title_func { }` | Set dynamic title |
| `.type(symbol)` | Set spinner type |
| `.action { }` | Set background action |
| `.with_accessible(bool)` | Enable static mode |
## Form Methods
| Method | Description |
|----------------------------------|------------------------------------------------|
| `.with_theme(theme)` | Apply theme to all fields |
| `.with_layout(layout)` | Set form layout |
| `.with_width(int)` | Set form width |
| `.with_height(int)` | Set form height |
| `.with_accessible(bool)` | Enable accessible mode |
| `.with_show_help(bool)` | Show/hide help |
| `.with_validate_on_submit(bool)` | Block navigation on validation errors |
| `.run` | Run the form, returns `Array[ValidationError]` |
| `.errors` | Get all validation errors |
| `.to_h` | Get all results as hash |
| `[key]` | Get result by key |
| `.get(key)` | Get result by key |
### Block-based DSL
**Alternative Ruby-idiomatic syntax using blocks:**
```ruby
form = Huh::Form.new do |form|
form.theme(Huh::Themes.charm)
form.group do |group|
group.title("User Registration")
group.input do |input|
input.key("name")
input.title("What's your name?")
input.placeholder("Enter your name")
end
group.select do |select|
select.key("role")
select.title("Your role")
select.options("Developer", "Designer", "Manager")
end
group.confirm do |confirm|
confirm.key("newsletter")
confirm.title("Subscribe?")
end
end
end
form.run
```
## Development
**Requirements:**
- Ruby 3.2+
**Install dependencies:**
```bash
bundle install
```
**Run tests:**
```bash
bundle exec rake test
```
**Run demos:**
```bash
./demo/basic
./demo/dsl
./demo/dynamic
./demo/spinner
./demo/validation
```
## Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/marcoroth/huh-ruby.
## License
The gem is available as open source under the terms of the MIT License.
## Acknowledgments
This gem is a Ruby port of [charmbracelet/huh](https://github.com/charmbracelet/huh), part of the excellent [Charm](https://charm.sh) ecosystem. Charm Ruby is not affiliated with or endorsed by Charmbracelet, Inc.
---
Part of [Charm Ruby](https://charm-ruby.dev).
[Lipgloss](https://github.com/marcoroth/lipgloss-ruby) • [Bubble Tea](https://github.com/marcoroth/bubbletea-ruby) • [Bubbles](https://github.com/marcoroth/bubbles-ruby) • [Glamour](https://github.com/marcoroth/glamour-ruby) • [Huh?](https://github.com/marcoroth/huh-ruby) • [Harmonica](https://github.com/marcoroth/harmonica-ruby) • [Bubblezone](https://github.com/marcoroth/bubblezone-ruby) • [Gum](https://github.com/marcoroth/gum-ruby) • [ntcharts](https://github.com/marcoroth/ntcharts-ruby)
The terminal doesn't have to be boring.
