Effortlessly beautiful CLI prompts for Ruby.
A faithful Ruby port of @clack/prompts.
- Beautiful by default - Thoughtfully designed prompts that just look right
- Vim-friendly - Navigate with
hjklor arrow keys - Accessible - Graceful ASCII fallbacks for limited terminals
- Composable - Group prompts together with
Clack.group
# Gemfile
gem "clack"
# Or from GitHub
gem "clack", github: "swhitt/clackrb"# Or install directly
gem install clackrequire "clack"
Clack.intro "project-setup"
result = Clack.group do |g|
g.prompt(:name) { Clack.text(message: "Project name?", placeholder: "my-app") }
g.prompt(:framework) do
Clack.select(
message: "Pick a framework",
options: [
{ value: "rails", label: "Ruby on Rails", hint: "recommended" },
{ value: "sinatra", label: "Sinatra" },
{ value: "roda", label: "Roda" }
]
)
end
g.prompt(:features) do
Clack.multiselect(
message: "Select features",
options: %w[api auth admin websockets]
)
end
end
if Clack.cancel?(result)
Clack.cancel("Setup cancelled")
exit 1
end
Clack.outro "You're all set!"Try it yourself:
ruby examples/full_demo.rbAll prompts return the user's input, or Clack::CANCEL if they pressed Escape/Ctrl+C.
# Always check for cancellation
result = Clack.text(message: "Name?")
exit 1 if Clack.cancel?(result)name = Clack.text(
message: "What is your project named?",
placeholder: "my-project", # Shown when empty (dim)
default_value: "untitled", # Used if submitted empty
initial_value: "hello-world", # Pre-filled, editable
validate: ->(v) { "Required!" if v.empty? }
)secret = Clack.password(
message: "Enter your API key",
mask: "*" # Default: "▪"
)proceed = Clack.confirm(
message: "Deploy to production?",
active: "Yes, ship it!",
inactive: "No, abort",
initial_value: false
)Single selection with keyboard navigation.
db = Clack.select(
message: "Choose a database",
options: [
{ value: "pg", label: "PostgreSQL", hint: "recommended" },
{ value: "mysql", label: "MySQL" },
{ value: "sqlite", label: "SQLite", disabled: true }
],
initial_value: "pg",
max_items: 5 # Enable scrolling
)Multiple selections with toggle controls.
features = Clack.multiselect(
message: "Select features to install",
options: [
{ value: "api", label: "API Mode" },
{ value: "auth", label: "Authentication" },
{ value: "jobs", label: "Background Jobs" }
],
initial_values: ["api"],
required: true, # Must select at least one
max_items: 5 # Enable scrolling
)Shortcuts: Space toggle | a all | i invert
Type to filter from a list of options.
color = Clack.autocomplete(
message: "Pick a color",
options: %w[red orange yellow green blue indigo violet],
placeholder: "Type to search..."
)Type to filter with multi-selection support.
colors = Clack.autocomplete_multiselect(
message: "Pick colors",
options: %w[red orange yellow green blue indigo violet],
placeholder: "Type to filter...",
required: true, # At least one selection required
initial_values: ["red"] # Pre-selected values
)Shortcuts: Space toggle | a toggle all | i invert | Enter confirm
File/directory path selector with filesystem navigation.
project_dir = Clack.path(
message: "Where should we create your project?",
only_directories: true, # Only show directories
root: "." # Starting directory
)Navigation: Type to filter | Tab to autocomplete | ↑↓ to select
Quick selection using keyboard shortcuts.
action = Clack.select_key(
message: "What would you like to do?",
options: [
{ value: "create", label: "Create new project", key: "c" },
{ value: "open", label: "Open existing", key: "o" },
{ value: "quit", label: "Quit", key: "q" }
]
)Non-blocking animated indicator for async work.
spinner = Clack.spinner
spinner.start("Installing dependencies...")
# Do your work...
sleep 2
spinner.stop("Dependencies installed!")
# Or: spinner.error("Installation failed")
# Or: spinner.cancel("Cancelled")Visual progress bar for measurable operations.
progress = Clack.progress(total: 100, message: "Downloading...")
progress.start
files.each_with_index do |file, i|
download(file)
progress.update(i + 1)
end
progress.stop("Download complete!")Run multiple tasks with status indicators.
results = Clack.tasks(tasks: [
{ title: "Checking dependencies", task: -> { check_deps } },
{ title: "Building project", task: -> { build } },
{ title: "Running tests", task: -> { run_tests } }
])Multiselect with options organized into groups.
features = Clack.group_multiselect(
message: "Select features",
options: [
{
label: "Frontend",
options: [
{ value: "hotwire", label: "Hotwire" },
{ value: "stimulus", label: "Stimulus" }
]
},
{
label: "Background",
options: [
{ value: "sidekiq", label: "Sidekiq" },
{ value: "solid_queue", label: "Solid Queue" }
]
}
]
)Chain multiple prompts and collect results in a hash. Cancellation is handled automatically.
result = Clack.group do |g|
g.prompt(:name) { Clack.text(message: "Your name?") }
g.prompt(:email) { Clack.text(message: "Your email?") }
g.prompt(:confirm) { |r| Clack.confirm(message: "Create account for #{r[:email]}?") }
end
return if Clack.cancel?(result)
puts "Welcome, #{result[:name]}!"Handle cancellation with a callback:
Clack.group(on_cancel: ->(r) { cleanup(r) }) do |g|
# prompts...
endBeautiful, consistent log messages.
Clack.log.info("Starting build...")
Clack.log.success("Build completed!")
Clack.log.warn("Cache is stale")
Clack.log.error("Build failed")
Clack.log.step("Running migrations")
Clack.log.message("Custom message")Stream output from iterables, enumerables, or shell commands:
# Stream from an array or enumerable
Clack.stream.info(["Line 1", "Line 2", "Line 3"])
Clack.stream.step(["Step 1", "Step 2", "Step 3"])
# Stream from a shell command (returns true/false for success)
success = Clack.stream.command("npm install", type: :info)
# Stream from any IO or StringIO
Clack.stream.success(io_stream)Display important information in a box.
Clack.note(<<~MSG, title: "Next Steps")
cd my-project
bundle install
bin/rails server
MSGRender a customizable bordered box.
Clack.box("Hello, World!", title: "Greeting")
# With options
Clack.box(
"Centered content",
title: "My Box",
content_align: :center, # :left, :center, :right
title_align: :center,
width: 40, # or :auto to fit content
rounded: true # rounded or square corners
)Streaming log that clears on success and shows full output on failure. Useful for build output.
tl = Clack.task_log(title: "Building...", limit: 10)
tl.message("Compiling file 1...")
tl.message("Compiling file 2...")
# On success: clears the log
tl.success("Build complete!")
# On error: keeps the log visible
# tl.error("Build failed!")Clack.intro("my-cli v1.0") # ┌ my-cli v1.0
# ... your prompts ...
Clack.outro("Done!") # └ Done!
# Or on error:
Clack.cancel("Aborted") # └ Aborted (red)- Ruby 3.2+
- No runtime dependencies
bundle install
bundle exec rake # Lint + tests
bundle exec rake spec # Tests only
COVERAGE=true bundle exec rake spec # With coverageThis is a Ruby port of Clack, created by Nate Moore and the Astro team.
MIT - See LICENSE
