Skip to content
UrushiDocumentation

Prompt overview

urushi-prompt is for short decisions made while a command is running: asking for text, choosing from known values, confirming an action, or combining those steps into a form. A form temporarily owns terminal interaction, then returns submitted typed values or cancellation to the command.

Prefer flags and arguments when the complete operation can be specified before execution. Use a prompt when the user needs context from the running command to make the decision.

This form collects text, moves focus into a typed selection, confirms the choice, restores the terminal, and returns values to the command:

Type Aki, choose Formal, accept the default confirmation, and receive ordinary program output after the prompt session closes. The recording runs the real urushi-prompt runtime.

The moving focus rail, selection highlight, contextual help, and final Good day, Aki. are all part of the same blocking Form::run call. The code below builds that shape; the field guides explain each behavior separately.

Run the complete Prompt quickstart ↓

Terminal window
cargo add urushi-prompt
Cargo.toml
[dependencies]
urushi-prompt = "0.1.0"

The crate re-exports urushi for styling, so a prompt-only application does not need a separate direct dependency on the core crate.

The form model, field behavior, validation, and theme are configured together:

use urushi_prompt::{
Confirm, ConfirmAnswer, FieldKey, Form, FormOutcome, Group, Input, Select,
SelectOption,
urushi::ThemePreset,
};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let name_key = FieldKey::<String>::new("name");
let style_key = FieldKey::<String>::new("style");
let proceed_key = FieldKey::<ConfirmAnswer>::new("proceed");
let group = Group::builder()
.title("Greeting setup")
.description("Review three values before generating a greeting.")
.field(
Input::new(name_key.clone(), "What is your name?", "")?
.placeholder("e.g. Alex")
.required(),
)
.field(Select::new(
style_key.clone(),
"Choose a greeting style",
vec![
SelectOption::new("Friendly", "friendly".to_owned()),
SelectOption::new("Formal", "formal".to_owned()),
],
)?)
.field(Confirm::new(
proceed_key.clone(),
"Generate the greeting?",
Some(true),
)?)
.build()?;
let form = Form::builder().group(group).build()?;
let theme = ThemePreset::get("Catppuccin Mocha")
.expect("built-in theme")
.theme();
if let FormOutcome::Submitted(values) = form.run(&theme)? {
let name = values.get(&name_key).expect("submitted name");
let style = values.get(&style_key).expect("submitted style");
let proceed = values.get(&proceed_key).expect("submitted confirmation");
let greeting = match (style.as_str(), proceed.value) {
(_, false) => "No greeting generated.".to_owned(),
("formal", true) => format!("Good day, {name}."),
(_, true) => format!("Hi, {name}!"),
};
println!("{greeting}");
}
Ok(())
}

When the form opens, the active field is marked by the focused gutter and the remaining fields stay visible as space allows:

Greeting setup
Review three values before generating a greeting.

┃ What is your name?
┃ › e.g. Alex

  Choose a greeting style
  › Friendly
    Formal

  Generate the greeting?
  Yes  No

  enter continue • shift+tab back • esc cancel

After the user enters Aki, chooses Formal, accepts Yes, and submits, the command resumes with typed values:

Program output
Good day, Aki.

FormOutcome::Submitted contains the typed values addressed by the three keys; FormOutcome::Cancelled contains no partial values.

Need Use See it in the recording
Collect editable text Input Aki remains visible after advancing
Choose a typed value Select<T> the blue selection moves to Formal
Confirm an action Confirm Yes is focused and submitted
Validate before advancing required/custom validators the field keeps its value and shows the error
Navigate a guided flow groups and ordered fields the focus rail moves through one form
Return typed values or cancellation FieldKey<T> and FormOutcome ordinary output appears only after restoration

Longer selections can filter and scroll a bounded set of rows. Inline placement can begin on a new line, reuse the current line, or start at a known column. Those settings are detailed in the task guides rather than duplicated in the first runnable example.

Blocking prompt lifecycleA configured form opens an inline terminal session. The active field handles input and validation until the form returns either typed submitted values or cancellation, then restores the terminal session.CONFIGUREFormgroups · typed fieldstheme · placementBLOCKING SESSIONActive fieldedit · choose · confirmhelp + focused presentationValidationkeep value + show messageOUTCOMESubmittedFormValues + FieldKey<T>Cancelled / failedno partial valuesinvalidcompleteesc / errorForm::run owns input, redraw, cursor placement, and restoration for this interval

Form::run is blocking. It owns input, redraws, cursor placement, and terminal session restoration until the form is submitted, cancelled, resized beyond the selected policy, or fails. The supplied core Theme determines its visual roles, so prompts can match the application’s static output.

The default inline presentation keeps surrounding output in the primary buffer and leaves the answered form in scrollback. Its origin, width, and resize policy are explicit because terminal reflow can make a previously drawn region impossible to locate safely.

Form::run opens the default connection for a convenient inline prompt. Form::run_with_terminal is the public caller-owned path for applications that already opened an interactive connection. Both use the inline presentation in 0.1.0. The prompt architecture also defines an alternate-screen presentation that shares the same form and field model, but selecting that presentation is not yet public.