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 Input

The TextInput widget provides single-line text editing with support for selection, clipboard operations, undo/redo, and password masking.

Basic Usage

#![allow(unused)]
fn main() {
let username = create_signal(String::new());

text_input(username)
}

TextInput uses two-way binding with signals:

  • When the user types, the signal is automatically updated
  • When the signal changes programmatically, the input reflects the new value

No manual synchronization is needed - just pass a Signal<String> and the binding works automatically.

Styling

Text Color

#![allow(unused)]
fn main() {
text_input(value)
    .text_color(Color::WHITE)
}

Cursor Color

#![allow(unused)]
fn main() {
text_input(value)
    .cursor_color(Color::rgb(0.4, 0.8, 1.0))
}

Selection Color

#![allow(unused)]
fn main() {
text_input(value)
    .selection_color(Color::rgba(0.4, 0.6, 1.0, 0.4))
}

Font Size

#![allow(unused)]
fn main() {
text_input(value)
    .font_size(16.0)
}

Font Family

#![allow(unused)]
fn main() {
// Predefined families
text_input(value)
    .font_family(FontFamily::Monospace)

// Shorthand for monospace
text_input(value)
    .mono()

// Custom font
text_input(value)
    .font_family(FontFamily::Name("JetBrains Mono".into()))
}

Font Weight

#![allow(unused)]
fn main() {
// Using constants
text_input(value)
    .font_weight(FontWeight::BOLD)

// Shorthand for bold
text_input(value)
    .bold()
}

Password Mode

Hide text input for sensitive data like passwords:

#![allow(unused)]
fn main() {
text_input(password)
    .password(true)
}

By default, characters are masked with . Customize the mask character:

#![allow(unused)]
fn main() {
text_input(password)
    .password(true)
    .mask_char('*')
}

Callbacks

On Change

Called whenever the text content changes:

#![allow(unused)]
fn main() {
text_input(value)
    .on_change(|new_text| {
        println!("Text changed: {}", new_text);
    })
}

On Submit

Called when the user presses Enter:

#![allow(unused)]
fn main() {
text_input(value)
    .on_submit(|text| {
        println!("Submitted: {}", text);
    })
}

Keyboard Shortcuts

The TextInput widget supports standard text editing shortcuts:

ShortcutAction
Ctrl+ASelect all
Ctrl+CCopy selection
Ctrl+XCut selection
Ctrl+VPaste
Ctrl+ZUndo
Ctrl+Shift+Z or Ctrl+YRedo
Left/RightMove cursor
Ctrl+Left/RightMove by word
Shift+Left/RightExtend selection
Home/EndMove to start/end
BackspaceDelete before cursor
DeleteDelete after cursor

Styling with Container

TextInput handles text editing but not visual styling like backgrounds and borders. Wrap it in a Container for full styling:

#![allow(unused)]
fn main() {
container()
    .padding(Padding::horizontal(12.0).vertical(8.0))
    .background(Color::rgb(0.15, 0.15, 0.2))
    .border(1.0, Color::rgb(0.3, 0.3, 0.4))
    .corner_radius(4.0)
    .child(
        text_input(value)
            .text_color(Color::WHITE)
            .font_size(14.0)
    )
}

With Focus State

Add visual feedback when the input is focused:

#![allow(unused)]
fn main() {
container()
    .padding(Padding::horizontal(12.0).vertical(8.0))
    .background(Color::rgb(0.15, 0.15, 0.2))
    .border(1.0, Color::rgb(0.3, 0.3, 0.4))
    .corner_radius(4.0)
    .focused_state(|s| s.border_color(Color::rgb(0.4, 0.6, 1.0)))
    .child(
        text_input(value)
            .text_color(Color::WHITE)
    )
}

Complete Example

A login form with username and password fields:

#![allow(unused)]
fn main() {
fn login_form() -> Container {
    let username = create_signal(String::new());
    let password = create_signal(String::new());

    container()
        .padding(24.0)
        .background(Color::rgb(0.1, 0.1, 0.15))
        .corner_radius(12.0)
        .layout(Flex::column().spacing(16.0))
        .children([
            // Username field
            container()
                .layout(Flex::column().spacing(4.0))
                .children([
                    text("Username")
                        .font_size(12.0)
                        .color(Color::rgb(0.6, 0.6, 0.7)),
                    container()
                        .padding(Padding::horizontal(12.0).vertical(8.0))
                        .background(Color::rgb(0.15, 0.15, 0.2))
                        .border(1.0, Color::rgb(0.3, 0.3, 0.4))
                        .corner_radius(4.0)
                        .focused_state(|s| s.border_color(Color::rgb(0.4, 0.6, 1.0)))
                        .child(
                            text_input(username)
                                .text_color(Color::WHITE)
                                .font_size(14.0)
                        ),
                ]),
            // Password field
            container()
                .layout(Flex::column().spacing(4.0))
                .children([
                    text("Password")
                        .font_size(12.0)
                        .color(Color::rgb(0.6, 0.6, 0.7)),
                    container()
                        .padding(Padding::horizontal(12.0).vertical(8.0))
                        .background(Color::rgb(0.15, 0.15, 0.2))
                        .border(1.0, Color::rgb(0.3, 0.3, 0.4))
                        .corner_radius(4.0)
                        .focused_state(|s| s.border_color(Color::rgb(0.4, 0.6, 1.0)))
                        .child(
                            text_input(password)
                                .password(true)
                                .text_color(Color::WHITE)
                                .font_size(14.0)
                        ),
                ]),
            // Submit button
            container()
                .padding(Padding::horizontal(16.0).vertical(10.0))
                .background(Color::rgb(0.3, 0.5, 0.9))
                .corner_radius(6.0)
                .hover_state(|s| s.lighter(0.1))
                .pressed_state(|s| s.darker(0.1))
                .on_click(move || {
                    println!("Login: {} / {}", username.get(), password.get());
                })
                .child(
                    text("Sign In")
                        .color(Color::WHITE)
                        .font_size(14.0)
                        .bold()
                ),
        ])
}
}

Features

  • Selection: Click and drag to select text, or use Shift+Arrow keys
  • Clipboard: Full copy/cut/paste support via Ctrl+C/X/V
  • Undo/Redo: History with intelligent coalescing of rapid edits
  • Scrolling: Long text scrolls horizontally to keep cursor visible
  • Cursor Blinking: Standard blinking cursor when focused
  • Key Repeat: Hold keys for continuous input

API Reference

#![allow(unused)]
fn main() {
text_input(signal: Signal<String>) -> TextInput

impl TextInput {
    pub fn text_color<M>(self, color: impl IntoSignal<Color, M>) -> Self;
    pub fn cursor_color<M>(self, color: impl IntoSignal<Color, M>) -> Self;
    pub fn selection_color<M>(self, color: impl IntoSignal<Color, M>) -> Self;
    pub fn font_size<M>(self, size: impl IntoSignal<f32, M>) -> Self;
    pub fn font_family<M>(self, family: impl IntoSignal<FontFamily, M>) -> Self;
    pub fn font_weight<M>(self, weight: impl IntoSignal<FontWeight, M>) -> Self;
    pub fn bold(self) -> Self;      // Shorthand for FontWeight::BOLD
    pub fn mono(self) -> Self;      // Shorthand for FontFamily::Monospace
    pub fn password(self, enabled: bool) -> Self;
    pub fn mask_char(self, c: char) -> Self;
    pub fn on_change<F: Fn(&str) + 'static>(self, callback: F) -> Self;
    pub fn on_submit<F: Fn(&str) + 'static>(self, callback: F) -> Self;
}
}

Note: The on_change callback is optional and is called in addition to the automatic signal update. Use it for side effects like validation or logging, not for updating the signal (that happens automatically).