Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Text entry

Single-line input, multi-line textarea, dropdown select, filterable combobox, and multi-select with checkboxes.

Input

A single-line text field with cursor, horizontal scrolling, selection, history, and optional validation or autocomplete.

extern crate tuile;
extern crate ratatui_core;
use tuile::prelude::*;

fn demo(area: Rect, buf: &mut Buffer) {
let mut state = InputState::new();
Input::new()
    .placeholder("Name")
    .focused(true)
    .render(area, buf, &mut state);

if state.take_submitted() {
    let value = state.value();
}
}
fn main() {}

State

InputState owns:

FieldTypePurpose
valueStringCurrent text content
cursorusizeGrapheme index of the cursor
scrollusizeHorizontal scroll offset
selectionOption<(usize, usize)>Selection anchor and cursor (if active)
submittedboolTrue when Enter was pressed; read via take_submitted()
errorOption<String>Validation error message (if validator is set)

Create state with InputState::new() or InputState::with_value("text"). Read the value with state.value(), set it with state.set_value("text"), or select all with state.select_all().

Keys

KeyAction
Left / RightMove cursor one grapheme
Ctrl-Left / Ctrl-RightMove to word boundary
Home / EndJump to line start or end
Ctrl-ASelect all
Shift-Left / Shift-RightExtend selection
BackspaceDelete character before cursor (or selection)
DeleteDelete character at cursor
Ctrl-BackspaceDelete word before cursor
Ctrl-UDelete from cursor to start
Ctrl-KDelete from cursor to end
Ctrl-WDelete word before cursor
Ctrl-XCut selection to clipboard
Ctrl-CCopy selection to clipboard
Ctrl-VPaste from clipboard
Ctrl-ZUndo (restores previous history entry)
TabAccept autocomplete suggestion (if suggester is set)
EnterSubmit (sets submitted flag)
Right at endAccept suggestion if cursor is at end of line

Mouse

Click positions the cursor. Drag selects text. The widget tracks double-click timing but full text selection on double-click depends on the render implementation.

Options

MethodEffect
placeholder(s)Placeholder text shown when empty
password(true)Mask input with dots
max_len(n)Limit input to n graphemes
restrict(r)Filter input: Digits, Integer, Number, Alpha, Alnum, Custom, or None
validator(fn)Validation function fn(&str) -> Result<(), String>; errors populate state.error
suggester(fn)Autocomplete function fn(&str) -> Option<String> returning suffix to append
prefix(s)Static prefix (e.g. $ for currency)
suffix(s)Static suffix (e.g. ms for time)
compact(true)Single-line compact mode without tall frame
shape(s)Frame shape (see FieldShape section)
tab_accepts(true)Let Tab accept suggestions (default: Tab is navigation)
show_error(true)Display validation error below the field
select_on_focus(true)Select all text when focused
align(a)Text alignment: Left, Center, Right

The restrict option filters typed characters. Digits allows only 0-9. Integer allows digits and leading -. Number allows digits, ., -, +, e, E. Alpha allows letters. Alnum allows letters and digits. Custom uses a custom filter function passed to state.custom_filter.

TextArea

A multi-line text editor with line numbers, syntax highlighting hook, undo/redo, and scrolling.

extern crate tuile;
extern crate ratatui_core;
use tuile::prelude::*;

fn demo(area: Rect, buf: &mut Buffer) {
let mut state = TextAreaState::new();
TextArea::new()
    .line_numbers(true)
    .highlight_line(true)
    .focused(true)
    .render(area, buf, &mut state);

let text = state.text();
}
fn main() {}

State

TextAreaState owns:

FieldTypePurpose
linesVec<String>Lines of text content
cursor(usize, usize)Row and column (grapheme indices)
scroll_yusizeVertical scroll offset
scroll_xusizeHorizontal scroll offset
selectionOption<(usize, usize, usize, usize)>Selection rectangle (row0, col0, row1, col1)
vscroll_state / hscroll_stateScrollbarStateScrollbar positions

Create state with TextAreaState::new() or TextAreaState::with_text("multi\nline"). Read all text with state.text(), set it with state.set_text("text"), or insert at cursor with state.insert_str("text"). The undo and redo stacks store up to 100 snapshots each.

Keys

KeyAction
Up / DownMove cursor one line
Left / RightMove cursor one grapheme (wraps at line boundaries)
Ctrl-Left / Ctrl-RightMove to word boundary
Home / EndJump to line start or end
Ctrl-Home / Ctrl-EndJump to document start or end
PageUp / PageDownMove 10 lines
Ctrl-ASelect all
Ctrl-ZUndo
Ctrl-YRedo
Ctrl-DDuplicate current line
Alt-Up / Alt-DownMove current line up or down
Ctrl-XCut selection to clipboard
Ctrl-CCopy selection to clipboard
Ctrl-VPaste from clipboard
BackspaceDelete character before cursor (or selection)
DeleteDelete character at cursor
EnterInsert newline (with auto-indent)
TabInsert 4 spaces

Mouse

Click positions the cursor. Drag selects a rectangular region. Wheel scrolls vertically.

Options

MethodEffect
line_numbers(true)Show line numbers in left gutter
highlight_line(true)Highlight the current line
read_only(true)Disable editing
tab_size(n)Tab width in spaces (default 4)
max_lines(n)Limit to n lines
placeholder(s)Placeholder text shown when empty
shape(s)Frame shape (see FieldShape section)
highlighter(fn)Syntax highlighting function fn(&str) -> Vec<(usize, usize, Style)> returning (start, end, style) spans for a line
cursor(style)Cursor style: Block, Bar, Underline, or Outline
cursor_blink(true)Enable cursor blinking
cursor_when_unfocused(true)Show cursor even when not focused
show_position(true)Display Ln X, Col Y position indicator

The highlighter hook is called once per visible line with the line text. Return a list of (start_byte, end_byte, Style) tuples. The showcase page includes a simple Rust highlighter that matches keywords and strings.

Cursor styles

CursorStyle controls how the cursor is drawn:

VariantAppearance
BlockInverse cell (default)
BarThin ▏ bar in cursor color
UnderlineGlyph underlined, bold, accent foreground
OutlineCell background set to cursor_blurred_bg, foreground unchanged

Select

A dropdown with a fixed list of options. Click or press Space/Enter to open, arrow keys to navigate, Enter to choose.

extern crate tuile;
extern crate ratatui_core;
use tuile::prelude::*;

fn demo(area: Rect, buf: &mut Buffer) {
let mut state = SelectState::new(&["Red", "Green", "Blue"]);
Select::new()
    .placeholder("Pick a color")
    .focused(true)
    .render(area, buf, &mut state);

if let Some(idx) = state.selected() {
    let label = state.selected_label().unwrap();
}
}
fn main() {}

State

SelectState owns:

FieldTypePurpose
optionsVec<SelectOption>List of selectable options
selectedOption<usize>Index of selected option
openboolTrue when dropdown is open
highlightusizeIndex of highlighted option in open dropdown
scrollusizeScroll offset in dropdown
dropdown_widthDropdownWidthWidth policy: Auto, Field, or Fixed(u16)

Create state with SelectState::new(&["A", "B", "C"]) or SelectState::with_options(vec). Read selection with state.selected() or state.selected_label(). Set selection with state.set_selected(Some(idx)) or clear with state.set_selected(None). Close the dropdown with state.close().

Each SelectOption has a label (displayed text), optional value (stored data), and disabled flag.

Keys

When closed:

KeyAction
Space / Enter / DownOpen dropdown

When open:

KeyAction
EscClose without selecting
EnterSelect highlighted option and close
Up / DownMove highlight (skips disabled options)
Home / EndJump to first or last option
PageUp / PageDownMove highlight by 5
Any letterTypeahead: jump to next option starting with typed prefix (resets after 800ms)

Mouse

Click the field to open or close. Click an option to select it. Wheel scrolls the dropdown list. Click outside closes the dropdown (the parent must handle this by checking state.dropdown_area and calling state.close()).

Options

MethodEffect
placeholder(s)Placeholder text when no option is selected
max_visible(n)Maximum visible options before scrolling (default 8)
allow_blank(true)Allow deselecting the current option
compact(true)Compact mode (single-line field without tall frame)
shape(s)Frame shape (see FieldShape section)

Set state.dropdown_width to control dropdown width. DropdownWidth::Auto fits the longest option. DropdownWidth::Field matches the field width. DropdownWidth::Fixed(w) uses a fixed width.

Combobox

An editable select: type to filter options, arrow keys to navigate, Enter to choose. The input buffer filters the list in real time.

extern crate tuile;
extern crate ratatui_core;
use tuile::prelude::*;

fn demo(area: Rect, buf: &mut Buffer) {
let mut state = ComboboxState::new(&["Rust", "Python", "JavaScript"]);
Combobox::new()
    .placeholder("Type to filter")
    .focused(true)
    .render(area, buf, &mut state);

if let Some(idx) = state.selected() {
    let label = state.selected_label().unwrap();
}
}
fn main() {}

State

ComboboxState owns:

FieldTypePurpose
inputStringCurrent input buffer (filter query)
cursorusizeCursor position in input
optionsVec<SelectOption>All options
filteredVec<usize>Indices of options matching current input
selectedOption<usize>Index in options of selected option
highlightusizeIndex in filtered of highlighted option
openboolTrue when dropdown is open
scrollusizeScroll offset in dropdown

Create state with ComboboxState::new(&["A", "B"]). The filtered list is rebuilt on every keystroke, case-insensitive substring matching. Read selection with state.selected() or state.selected_label(). The input buffer is cleared when an option is selected.

Keys

KeyAction
DownOpen dropdown (if closed)
EscClose dropdown
EnterSelect highlighted option and close
Up / DownNavigate filtered options (opens dropdown if closed)
Home / EndJump to first or last filtered option
Left / RightMove cursor in input buffer
Backspace / DeleteEdit input buffer
Any letterAppend to filter and update filtered list

Mouse

Same as Select: click to open/close, click an option to select, wheel scrolls.

Options

MethodEffect
placeholder(s)Placeholder text when input is empty
max_visible(n)Maximum visible options before scrolling (default 8)
compact(true)Compact mode
shape(s)Frame shape (see FieldShape section)

MultiSelect

A select with checkboxes. Space toggles the highlighted option, Enter confirms and closes. The state tracks a set of selected indices.

extern crate tuile;
extern crate ratatui_core;
use tuile::prelude::*;

fn demo(area: Rect, buf: &mut Buffer) {
let mut state = MultiSelectState::new(&["HTML", "CSS", "JS"]);
MultiSelect::new()
    .placeholder("Select multiple")
    .focused(true)
    .render(area, buf, &mut state);

let selected = state.selected_indices();
let summary = state.summary();
}
fn main() {}

State

MultiSelectState owns:

FieldTypePurpose
optionsVec<SelectOption>List of options
selectedVec<bool>One flag per option (true = selected)
openboolTrue when dropdown is open
highlightusizeIndex of highlighted option
scrollusizeScroll offset

Create state with MultiSelectState::new(&["A", "B"]). Read selections with state.selected_indices() (returns Vec<usize> of selected indices) or state.summary() (returns "2 selected").

Keys

When closed:

KeyAction
Space / Enter / DownOpen dropdown

When open:

KeyAction
EscClose (selections remain)
EnterClose (selections remain)
SpaceToggle highlighted option
Up / DownMove highlight
Home / EndJump to first or last option
PageUp / PageDownMove highlight by 5

Mouse

Click the field to open. Click a checkbox to toggle that option. Click outside closes (the parent must handle this).

Options

MethodEffect
placeholder(s)Placeholder text when no options are selected
max_visible(n)Maximum visible options before scrolling (default 8)
compact(true)Compact mode
shape(s)Frame shape (see FieldShape section)

FieldShape

The FieldShape enum controls the frame drawn around text fields. All five widgets in this chapter accept shape(s) as a builder option. The default is Tall(Edge::Full), a Textual-style frame with thick side bars and thin top and bottom lines.

VariantAppearanceUse when
Tall(edge)Thick side bars (edge controls thickness) plus thin ▔/▁ lines above and belowYou want a prominent, Textual-style field (default)
Bars(edge)Left and right bars only, no top or bottomYou want vertical separation without horizontal lines
Bar(edge)Left bar onlyYou want a minimal accent (omp-style prompts)
RuleA ─ rule above and below, no side barsYou want horizontal separation in a dense form
RoundRounded box ╭─╮ / ╰─╯You want a softer, friendlier appearance
PromptA ❯ prompt glyph before the first line, no frameYou want a shell-style prompt (single-line only)
BandFull-width band in field color with padding rows and muted › promptYou want Claude Code’s composer style
NoneNo frameYou want inline editing or custom framing

The edge parameter controls bar thickness for Tall, Bars, and Bar:

EdgeWidthGlyph
Hair1/8 cell▏ / ▕
Thin2/8 cell▎ / ▕ (default)
Half1/2 cell▌ / ▐
FullWhole cellBackground painted solid

For example, shape(FieldShape::Bar(Edge::Hair)) draws a thin left accent bar. shape(FieldShape::Prompt) removes the frame entirely and draws a ❯ prompt.

Choosing between Select, Combobox, and MultiSelect

Use Select when:

  • The list is short (under 20 options) and the user knows what they want.
  • Typing is slower than arrow keys (e.g. “United States” vs. typing “uni”).
  • The list is stable and well-known (countries, months, enum values).

Use Combobox when:

  • The list is long (50+ options) and typing is faster than scrolling.
  • The user may not know the exact label (search by substring).
  • The list is dynamic or fetched from a search API.

Use MultiSelect when:

  • The user needs to choose zero or more options.
  • The choices are independent (tags, permissions, features).
  • You want a compact summary like “3 selected” instead of showing all chosen items.

If the user must pick exactly one from a long list and knows what they want, Combobox is the right choice. If they may not remember the label, Combobox lets them filter. If they need to pick several, MultiSelect is the only option in this family.