Skip to content

Input

Input is the low-level display component for a single-line input box. It does not take over the keyboard by itself and does not build in input exclusivity. You pass tui_input::Input state into it for rendering, then decide in the page handler which keys write text, which keys submit, and which keys exit.

input demo

This recording is generated by docs/tapes/input.tape. The example shows a controlled note input: type ratatui and submit, type manual control and submit, then type a draft and clear it with Esc.

cargo run --example input

Type text and press Enter to submit. When there is a draft, Esc clears it; when the input is empty, Esc exits.

The core prop is input: tui_input::Input. Usually you keep the real state in use_state and pass a snapshot during render:

let input = hooks.use_state(tui_input::Input::default);

Border(height: Constraint::Length(3)) {
    Input(
        input: input.read().clone(),
        placeholder: "Type a note and press Enter".to_string(),
        placeholder_style: Style::new().dark_gray(),
        cursor_style: Style::new().bg(Color::Yellow),
    )
}

Input only needs a single-line region. The example places it inside a three-line Border: the border consumes the top and bottom rows, leaving the middle row for input content.

Keyboard handling belongs to the page. tui-input already provides a crossterm backend, so call handle_event in your handler:

use ratatui_kit::prelude::tui_input::backend::crossterm::EventHandler;

hooks.use_event_handler(EventScope::Current, EventPriority::Normal, move |event| {
    let Event::Key(key) = &event else {
        return EventResult::Ignored;
    };

    match key.code {
        KeyCode::Enter => {
            let submitted = input.read().value().trim().to_string();
            input.write().reset();
            status.set(format!("submitted {submitted}"));
            EventResult::Consumed
        }
        _ => {
            input.write().handle_event(&event);
            EventResult::Consumed
        }
    }
});

The low-level Input does not know your submission rules, so the outer layer decides whether Enter, Esc, or Tab consumes events. In the example, all editing keys return Consumed so other handlers on the same layer do not also treat input keys as page shortcuts.

Input exposes four visual prop groups:

Input(
    style: Style::new().white(),
    placeholder_style: Style::new().dark_gray(),
    cursor_style: Style::new().bg(Color::Yellow),
    hide_cursor: false,
)

When input.value() is empty, it shows placeholder with placeholder_style; otherwise it uses style. The cursor is rendered as a single absolutely positioned Span. Horizontal scrolling comes from tui_input::Input::visual_scroll, so long text keeps the cursor visible.

For read-only display, recordings, or unfocused states, pass hide_cursor: true.

When you use Input directly, you own every input semantic: when editing activates, how validation works, whether to clear, and whether to open an exclusive input layer. This low-level form fits custom command lines, editor status bars, filters, and form fields.

For an ordinary search box, prefer SearchInput. It already wraps activation keys, submit, clear, validation hints, and input exclusivity.