C L A C K
CLI prompts for Ruby. Zero dependencies.
A faithful Ruby port of @clack/prompts
Recorded from examples/media/hero_demo.rb
See it in your own terminal:
gem install clack && clack-demoThe usual starting point:
print "Pick a framework (1=Rails, 2=Sinatra, 3=Hanami): "
framework = gets.chomp.to_iNo navigation, no validation, no feedback, and Ctrl+C ends in a stack trace. Clack replaces this with prompts that draw themselves, validate as the user types, and put the terminal back the way they found it.
gem "clack"Ruby 3.2 or newer. No runtime dependencies, no curses.
Thirteen prompts, plus spinners, progress bars, task lists, and styled output, all under one module. A complete flow:
require "clack"
Clack.intro "create-app"
project = 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: {rails: "Rails", sinatra: "Sinatra", hanami: "Hanami"})
end
g.prompt(:extras) do
Clack.multiselect(message: "Select extras",
options: {rspec: "RSpec", rubocop: "RuboCop"}, required: false)
end
end
exit if Clack.cancel?(project)
Clack.spin("Installing dependencies...") { sleep 2 }
Clack.outro "You're all set!"Clack.group collects answers into a Hash keyed by prompt name (project[:framework] # => :sinatra). Later prompts can read earlier answers (g.prompt(:port) { |answers| ... }), and Clack.group(on_cancel: ->(partial) { ... }) sees whatever was collected before the user bailed.
Every prompt takes message: plus its own options and returns the answer, or Clack::CANCEL when the user backs out.
| Prompt | Returns | Worth knowing |
|---|---|---|
Clack.text |
String | placeholder:, default_value:, initial_value:, tab completions: |
Clack.multiline_text |
String | Enter inserts a newline, Ctrl+D submits |
Clack.password |
String | masked input, custom mask: |
Clack.confirm |
Boolean | active:/inactive: labels, vertical: layout |
Clack.select |
one value | per-option hint:/disabled:, initial_value:, max_items: scrolling |
Clack.multiselect |
Array | Space toggles, a selects all, i inverts, required: |
Clack.autocomplete |
one value | fuzzy type-to-filter across label, value, and hint; custom filter: |
Clack.autocomplete_multiselect |
Array | fuzzy filter plus checkboxes |
Clack.select_key |
one value | answer with a single keypress, case_sensitive: |
Clack.group_multiselect |
Array | checkboxes under group headings, selectable_groups: |
Clack.path |
String | filesystem browser, root:, only_directories: |
Clack.date |
Date | segmented picker, :iso/:us/:eu formats, min:/max: |
Clack.range |
Numeric | slider over min:/max:/step:, fractional steps stay exact |
![]() Clack.text |
![]() Clack.select |
![]() Clack.multiselect |
![]() Clack.confirm |
Option lists accept a plain Hash ({pg: "PostgreSQL"} returns :pg) or full specs with hints:
db = Clack.select(
message: "Pick a database",
options: [
{value: :pg, label: "PostgreSQL", hint: "recommended"},
{value: :sqlite, label: "SQLite"},
{value: :mysql, label: "MySQL", disabled: true}
]
)Clack.group_multiselect takes a Hash of groups the same way:
addons = Clack.group_multiselect(
message: "Add-ons",
options: {
"Observability" => {prom: "Prometheus", dd: "Datadog"},
"Storage" => {s3: "S3", gcs: "GCS"}
}
)Every option of every prompt is covered in the prompt reference.
Escape and Ctrl+C never raise mid-prompt. The prompt returns the Clack::CANCEL sentinel, the terminal is restored (raw mode, cursor, half-drawn frames), and the script decides what happens next. CANCEL is a sentinel object, never nil, so an empty answer can't masquerade as a cancellation:
name = Clack.text(message: "Project name?")
exit 1 if Clack.handle_cancel(name) # prints "Cancelled", returns true
name = "my-app" if Clack.cancel?(name) # or check without printingThe same cleanup runs on exit, uncaught exceptions, and signals, so a crashed script never leaves the terminal in raw mode with a hidden cursor.
validate: takes a proc, a Regexp, a built-in Symbol, or an Array combining them. Return a String to reject, or Clack.warning for a soft failure the user can push through with a second Enter:
Clack.text(
message: "Project name?",
validate: [:required, /\A[a-z0-9_-]+\z/],
transform: :strip
)
Clack.text(
message: "Port?",
validate: ->(v) { Clack.warning("Ports below 1024 need root") if v.to_i < 1024 }
)Seventeen built-in validators, eleven transformers, and the full shorthand catalog live in validation and transforms.
Clack.spin("Installing dependencies...", success: "Installed") do |s|
sleep 1
s.message "Compiling assets..."
sleep 1
endThe block can exit, break, raise, or be interrupted; the spinner always prints a final line instead of leaving a half-drawn frame, even in forked children. Clack.spinner gives the same thing under manual start/stop/error control, and Clack.progress(total: 100) draws a determinate bar.
Clack.tasks runs a list sequentially, one spinner per step:
Clack.tasks(tasks: [
{title: "Fetching sources", task: -> { sleep 1 }},
{title: "Linking binaries", task: -> { sleep 1 }},
{title: "Deploying", task: -> { sleep 1 }, enabled: ENV["DEPLOY"] == "1"}
])Clack.log.info "Fetching origin"
Clack.log.success "Build passed"
Clack.log.warn "Cache is stale"
Clack.note "Run bin/setup to finish.", title: "Next steps"
Clack.stream.command("bundle install") # stream a subprocess line by line| Helper | Does |
|---|---|
Clack.intro / outro / cancel |
open, close, and abort the session rail |
Clack.log |
info, success, step, warn, error, message one-liners |
Clack.note / Clack.box |
bordered blocks, alignment and width options |
Clack.stream |
line-by-line output from enumerables, IOs, and shell commands |
Clack.task_log |
scrolling log that clears itself on success, stays visible on failure |
Clack.group |
collect prompts into one Hash with a single cancel path |
Prompts are drivable from tests, no TTY required:
require "clack/testing"
result = Clack::Testing.simulate(Clack.method(:select),
message: "Pick", options: %w[a b c]) do |p|
p.down
p.submit
end
result # => "b"The driver speaks arrows, typing, toggling, and cancellation, and works with RSpec, Minitest, or anything else. Details in testing.
Clack.update_settings(ci_mode: :auto)With :auto, prompts submit their defaults whenever input is not a TTY or a CI environment is detected, so the same script runs interactively and in a pipeline. Without it, prompting on piped stdin raises Clack::NotATerminalError with an actionable message rather than hanging.
Clack.update_settings(
with_guide: false, # hide the guide rail
show_instructions: false, # hide keyboard hint footers
messages: {cancel: "Abgebrochen"}, # localize built-in strings
aliases: {"y" => :enter, "n" => :cancel} # extra key bindings
)Both toggles also work per call, and instructions: replaces any prompt's hint text. Colors follow NO_COLOR and FORCE_COLOR, CLACK_UNICODE=0 forces the ASCII symbol set, and CLACK_ESCAPE_TIMEOUT widens the escape-detection window for laggy SSH links. Settings, key aliases, and environment variables are covered in configuration.
- Width is measured, not guessed: CJK text, fullwidth forms, emoji, and combining marks all line up.
- Arrow keys survive tmux and a vim exit: SS3 sequences are normalized, and the various Home/End encodings fold to one code per key.
- Narrow windows get a width warning rather than a mangled frame, and hint footers soft-wrap instead of corrupting the layout.
When something still looks wrong: troubleshooting.
Clack tracks upstream @clack/prompts: the same components, the same look, the same cancel-anywhere contract. On top of that, additions that only make sense in Ruby: the range slider, the validator and transformer toolkit, Clack::Testing, and CI mode.
Coming from tty-prompt? examples/migration_from_tty_prompt.rb maps the common patterns side by side.
- Prompt reference: every prompt, every option, every default
- Validation and transforms: built-ins and the shorthand catalog
- Configuration: settings, key aliases, environment variables
- Testing:
PromptDriver,KeyQueue, piped input - Troubleshooting: non-TTY input, arrow keys after vim/tmux, slow SSH
- Development: running the tests, recording the media
- Changelog
More runnable scripts live in examples/, including the demo behind clack-demo and the full-feature showcase.
Ported from @clack/prompts by Nate Moore and contributors. MIT.





