Build a form
This guide builds a form that asks for a name, a greeting style, and final confirmation.
Create typed keys
Section titled “Create typed keys”Keep each key so the submitted value can be retrieved with its correct type.
use urushi_prompt::{ConfirmAnswer, FieldKey};
let name_key = FieldKey::<String>::new("name");let style_key = FieldKey::<String>::new("style");let proceed_key = FieldKey::<ConfirmAnswer>::new("proceed");The key types determine the types returned after submission:
| Key | Submitted type |
|---|---|
name_key |
String |
style_key |
String |
proceed_key |
ConfirmAnswer |
An empty key name returns FieldConfigError::EmptyName; duplicate names return
FormBuildError::DuplicateFieldName when the form is built.
Build the fields and group
Section titled “Build the fields and group”use urushi_prompt::{Confirm, Group, Input, Select, SelectOption};
let group = Group::builder() .title("Greeting setup") .description("Choose how the greeting should be generated.") .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()?;This group renders all three fields in insertion order:
Greeting setup
Choose how the greeting should be generated.
┃ What is your name?
┃ › e.g. Alex
Choose a greeting style
› Friendly
Formal
Generate the greeting?
Yes No
Builder validation happens before terminal interaction: an empty group returns
GroupBuildError::EmptyGroup, an empty select option list returns
FieldConfigError::EmptyOptions, and duplicate field names are rejected when
the form is built.
Build and run the form
Section titled “Build and run the form”use urushi_prompt::{Form, FormOutcome};
let form = Form::builder().group(group).build()?;
match form.run(&theme)? { FormOutcome::Submitted(values) => { 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");
if proceed.value { println!("{style} greeting for {name}"); } } FormOutcome::Cancelled => eprintln!("Cancelled."),}When Form::run opens this form, all three fields share one inline region. The
active field is marked by the accent rail; muted help and placeholders retain
their theme roles.
1. Enter the name
Section titled “1. Enter the name”Greeting setup
Choose how the greeting should be generated.
┃ What is your name?
┃ › Alex
Choose a greeting style
› Friendly
Formal
Generate the greeting?
Yes No
enter continue • shift+tab back • esc cancel
2. Advance and choose a style
Section titled “2. Advance and choose a style”After Enter, the input is accepted and focus moves to the select. Down
makes Formal the typed selection:
Greeting setup
Choose how the greeting should be generated.
What is your name?
› Alex
┃ Choose a greeting style
┃ Friendly
┃ › Formal
Generate the greeting?
Yes No
↑/↓ select • enter continue • shift+tab back • esc cancel
3. Accept the confirmation default
Section titled “3. Accept the confirmation default”Enter advances to confirmation. Because this field has Some(true), Yes
is selected as the default and submitting it records ConfirmSource::Default:
What is your name?
› Alex
Choose a greeting style
Friendly
› Formal
┃ Generate the greeting?
┃ Yes No
←/→ choose • y yes • n no • enter submit • shift+tab back • esc cancel
4. Resume the command
Section titled “4. Resume the command”Submitting produces FormOutcome::Submitted; the code above prints:
formal greeting for AlexPressing Esc at any active field instead produces FormOutcome::Cancelled
and the code prints Cancelled. to stderr. No partial FormValues is exposed.
FormValues::get returns None if the key name or type does not match. A form
that reaches Submitted contains every accepted field value; cancellation
exposes no partial values.
Use the application theme
Section titled “Use the application theme”Pass the same Theme used for static output or a Ratatui adapter. Prompt roles
are derived from that supplied theme when the form starts. Terminal
capabilities determine which parts of the resulting logical styles can be
rendered; they do not select or replace the theme.
Form::run(&theme) opens the default terminal and uses the theme unchanged. It
does not query the terminal background. When automatic light/dark selection is
needed, query a caller-owned connection first and run the form on that same
connection:
Add urushi-terminal = "0.1.0" when the application opens the connection
itself. The native connection shown here is available on Unix.
use urushi_prompt::{ FormOutcome, urushi::{ColorScheme, ThemeMode},};use urushi_terminal::{TerminalQuery as _, backend::native::NativeTerminal};
let mode = ThemeMode::Auto { fallback: ColorScheme::Dark,};let mut terminal = NativeTerminal::open()?;let background = terminal.terminal_background()?;let theme = themes.select(mode.resolve(background));
match form.run_with_terminal(&mut terminal, theme)? { FormOutcome::Submitted(values) => { // Read the same typed keys used to build the form. } FormOutcome::Cancelled => eprintln!("Cancelled."),}If OSC 11 reports a light background, themes.select supplies the light theme;
if the query has no usable reply, this example supplies the dark fallback. The
prompt still has the same structure, but the selected semantic palette changes:
┃ What is your name?
┃ › e.g. Alex
enter continue • shift+tab back • esc cancel
ThemeMode::Auto classifies a successful OSC 11 observation and uses the
explicit fallback when the terminal provides no usable reply. The form then
queries size and rendering capabilities, draws the inline interaction, and
restores the caller-owned connection without querying its background again.
Next, configure fields and validation or control placement and resize behavior. The prompt reference lists every default and error.