A Rust TUI form builder crate built on Ratatui. Create terminal forms with a fluent builder API, pre-built field types, and composite blocks for common patterns like addresses and contact info.
Note: This crate was originally developed under the name
tform, but was renamed toratatui-formto avoid confusion with the unrelated tform crate. If you were using the old name, please update your dependencies toratatui-form.
- Fluent Builder API - Chain methods to build forms quickly
- Pre-built Fields - TextInput, Select (dropdown), Checkbox
- Composite Blocks - AddressBlock, ContactBlock, DateRangeBlock
- Validation - Required, Email, MinLength, MaxLength, Pattern (regex)
- Keyboard Navigation - Tab, Shift+Tab, Arrow keys
- Theming - Customizable styles with dark/light presets
- JSON Export - Serialize form data to JSON files
Add to your Cargo.toml:
[dependencies]
ratatui-form = "0.1.1"use std::io;
use crossterm::event::{self, Event};
use crossterm::terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen};
use crossterm::execute;
use ratatui::{backend::CrosstermBackend, Terminal};
use ratatui_form::{Form, FormResult, AddressBlock, Email};
fn main() -> io::Result<()> {
// Setup terminal
enable_raw_mode()?;
let mut stdout = io::stdout();
execute!(stdout, EnterAlternateScreen)?;
let backend = CrosstermBackend::new(stdout);
let mut terminal = Terminal::new(backend)?;
// Build the form
let mut form = Form::builder()
.title("Shipping Information")
.text("name", "Full Name")
.placeholder("John Doe")
.required()
.done()
.text("email", "Email")
.placeholder("john@example.com")
.required()
.validator(Box::new(Email))
.done()
.block(AddressBlock::new("shipping").required())
.checkbox("newsletter", "Subscribe to newsletter")
.done()
.build();
// Event loop
loop {
terminal.draw(|frame| {
form.render(frame.area(), frame.buffer_mut());
})?;
if let Event::Key(key) = event::read()? {
form.handle_input(key);
match form.result() {
FormResult::Submitted => {
form.write_json("output.json")?;
break;
}
FormResult::Cancelled => break,
FormResult::Active => {}
}
}
}
// Cleanup
disable_raw_mode()?;
execute!(terminal.backend_mut(), LeaveAlternateScreen)?;
Ok(())
}Single-line text input with cursor support.
Form::builder()
.text("username", "Username")
.placeholder("Enter username")
.required()
.initial_value("default")
.validator(Box::new(MinLength(3)))
.done()
.build()Dropdown selection with keyboard navigation.
Form::builder()
.select("priority", "Priority")
.option("low", "Low")
.option("medium", "Medium")
.option("high", "High")
.required()
.initial_value("medium")
.done()
.build()Toggle checkbox for boolean values.
Form::builder()
.checkbox("terms", "I agree to the terms")
.required() // Must be checked
.checked(false)
.done()
.build()Blocks are pre-configured groups of related fields.
US address with street, city, state (dropdown), and ZIP code validation.
use ratatui_form::AddressBlock;
Form::builder()
.block(AddressBlock::new("shipping").required())
.build()Creates fields: shipping_street1, shipping_street2, shipping_city, shipping_state, shipping_zip
Contact information with email validation.
use ratatui_form::ContactBlock;
Form::builder()
.block(ContactBlock::new("contact").required())
.build()Creates fields: contact_name, contact_email, contact_phone
Start and end date fields with YYYY-MM-DD format validation.
use ratatui_form::DateRangeBlock;
Form::builder()
.block(DateRangeBlock::new("trip").required())
.build()Creates fields: trip_start, trip_end
use ratatui_form::{Required, Email, MinLength, MaxLength, Pattern};
// Required - field cannot be empty
.validator(Box::new(Required))
// Email - valid email format
.validator(Box::new(Email))
// MinLength - minimum character count
.validator(Box::new(MinLength(3)))
// MaxLength - maximum character count
.validator(Box::new(MaxLength(100)))
// Pattern - custom regex
.validator(Box::new(Pattern::new(r"^\d{3}-\d{4}$", "Invalid format")))
// Pre-built patterns
.validator(Box::new(Pattern::zip_code())) // US ZIP code
.validator(Box::new(Pattern::phone())) // US phone number
.validator(Box::new(Pattern::date())) // YYYY-MM-DDImplement the Validator trait:
use ratatui_form::Validator;
struct EvenNumber;
impl Validator for EvenNumber {
fn validate(&self, value: &str) -> Result<(), String> {
match value.parse::<i32>() {
Ok(n) if n % 2 == 0 => Ok(()),
Ok(_) => Err("Must be an even number".to_string()),
Err(_) => Err("Must be a number".to_string()),
}
}
}| Key | Action |
|---|---|
Tab |
Next field |
Shift+Tab |
Previous field |
Up / Down |
Navigate fields (or dropdown options when open) |
Enter |
Submit form (on button) / Select option (in dropdown) |
Space |
Toggle checkbox / Open dropdown |
Esc |
Cancel form / Close dropdown |
Left / Right |
Move cursor in text fields |
Backspace |
Delete character before cursor |
Delete |
Delete character at cursor |
Ctrl+A |
Move cursor to start |
Ctrl+E |
Move cursor to end |
Ctrl+U |
Clear field |
use ratatui_form::FormStyle;
// Dark theme (default)
Form::builder()
.style(FormStyle::dark())
.build()
// Light theme
Form::builder()
.style(FormStyle::light())
.build()use ratatui_form::FormStyle;
use ratatui::style::{Color, Modifier, Style};
let custom_style = FormStyle::new()
.title(Style::default().fg(Color::Magenta).add_modifier(Modifier::BOLD))
.label(Style::default().fg(Color::White))
.label_focused(Style::default().fg(Color::Yellow))
.input(Style::default().fg(Color::White).bg(Color::DarkGray))
.input_focused(Style::default().fg(Color::White).bg(Color::Blue))
.error(Style::default().fg(Color::Red))
.button(Style::default().fg(Color::White).bg(Color::DarkGray))
.button_focused(Style::default().fg(Color::Black).bg(Color::Green));
Form::builder()
.style(custom_style)
.build()Forms serialize to flat JSON with field IDs as keys:
// After form.write_json("output.json"){
"name": "John Doe",
"email": "john@example.com",
"shipping_street1": "123 Main St",
"shipping_street2": "",
"shipping_city": "Springfield",
"shipping_state": "IL",
"shipping_zip": "62701",
"newsletter": true
}Access form data programmatically:
let data = form.to_json();
println!("{}", serde_json::to_string_pretty(&data)?);Run the included example:
cargo run --example address_formMIT