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:
| Shortcut | Action |
|---|---|
Ctrl+A | Select all |
Ctrl+C | Copy selection |
Ctrl+X | Cut selection |
Ctrl+V | Paste |
Ctrl+Z | Undo |
Ctrl+Shift+Z or Ctrl+Y | Redo |
Left/Right | Move cursor |
Ctrl+Left/Right | Move by word |
Shift+Left/Right | Extend selection |
Home/End | Move to start/end |
Backspace | Delete before cursor |
Delete | Delete 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).