Textarea
Textarea is the styled control for ordinary multi-line text. Use
Input for a single line and Editor for source
code.
#Import
use gpui_kit::component::input::{Textarea, TextareaState};// Installed in ui/components. That directory is on the Slint include path, so import by file name:
import { Textarea } from "textarea.slint";use iced::widget::text_editor::{Action, Content};
use iced::widget::{column, container, mouse_area, row, scrollable, svg, text};
use iced::{Alignment, Border, Color, Element, Length, Task, Theme};#Basic usage
let notes = cx.new(|cx| {
TextareaState::new(window, cx)
.rows(5)
.placeholder("Notes")
});
Textarea::new(¬es)Textarea {
placeholder: "Tell us about yourself…";
text <=> root.bio;
}let basic = column![
Textarea::new(&app.bio)
.id("bio")
.placeholder("Tell us about yourself…")
.on_action(Message::Bio),
]
.spacing(12.0);#Auto-grow
let message = cx.new(|cx| {
TextareaState::new(window, cx)
.auto_grow(2, 8)
.placeholder("Write a message")
});
Textarea::new(&message)VerticalLayout {
spacing: 12px;
Textarea {
height: 200px;
placeholder: "Release notes…";
text <=> root.notes;
}
}// `Length::Shrink` measures the content, so this field really grows; the
// Slint component cannot measure itself and draws a fixed taller box. Past
// the maximum it scrolls, which is the textarea's counterpart of max_rows.
let auto_grow = column![
Textarea::new(&app.notes)
.placeholder("Release notes…")
.auto_grow(2, 8)
.on_action(Message::Notes),
]
.spacing(12.0);The control grows until max_rows; overflowing content then scrolls.
#Value and events
let value = notes.read(cx).value();
notes.update(cx, |state, cx| {
state.set_value("Updated notes", window, cx);
});
cx.subscribe(¬es, |this, state, event: &InputEvent, cx| {
if matches!(event, InputEvent::Change) {
this.notes = state.read(cx).value();
cx.notify();
}
});VerticalLayout {
spacing: 8px;
alignment: start;
Textarea {
placeholder: "Type a note…";
edited(text) => {
root.draft = text;
}
}
Text {
text: "Draft: " + (root.draft == "" ? "(empty)" : root.draft);
color: Theme.muted-foreground;
font-size: Theme.font-size-xs;
wrap: word-wrap;
}
}// The caller owns the `Content`: `on_action` reports every interaction, the
// caller applies it with `perform`, and the text is read back from the same
// value. Reading is `content.text()`, setting is a fresh `Content`.
let events = column![
Textarea::new(&app.draft)
.placeholder("Type a note…")
.rows(4)
.on_action(Message::Draft),
muted_line(&format!(
"Draft: {}",
if app.draft.is_empty() {
"(empty)".to_string()
} else {
app.draft.text()
}
)),
muted_line(&format!(
"Last event: {}",
app.last_event.unwrap_or("nothing yet")
)),
]
.spacing(8.0);insert, replace, cursor_position, soft_wrap, searchable, and
submit_on_enter are available on TextareaState.
#Sizes
Textarea::new(¬es).large()
Textarea::new(¬es) // medium (default)
Textarea::new(¬es).small()VerticalLayout {
spacing: 12px;
Textarea {
size: ControlSize.small;
placeholder: "Small";
}
Textarea {
size: ControlSize.medium;
placeholder: "Medium";
}
Textarea {
size: ControlSize.large;
placeholder: "Large";
}
}// The size steps the text size and the padding together; each one stands
// three of that size's control heights tall unless `rows` or `auto_grow`
// says otherwise.
let sizes = column![
Textarea::new(&app.small)
.size(ControlSize::Small)
.placeholder("Small")
.on_action(Message::Small),
Textarea::new(&app.medium)
.size(ControlSize::Medium)
.placeholder("Medium")
.on_action(Message::Medium),
Textarea::new(&app.large)
.size(ControlSize::Large)
.placeholder("Large")
.on_action(Message::Large),
]
.spacing(12.0);The size changes the text size and the padding around the text together. It does
not set the height: use h for a fixed height, and let rows or auto_grow
decide the height of a growing textarea.
#Appearance
Textarea::new(¬es)
.h(px(160.))
.bordered(true)
.disabled(false)
.readonly(false)
.aria_label("Notes")VerticalLayout {
spacing: 12px;
Textarea {
placeholder: "Normal";
}
Textarea {
placeholder: "Read only";
read-only: true;
text: "Generated from the manifest.";
}
Textarea {
placeholder: "Invalid";
invalid: true;
text <=> root.snippet;
}
Textarea {
placeholder: "Disabled";
enabled: false;
}
}// The four states the Slint component draws. Read-only swallows the key
// bindings that edit and keeps the frame, the caret and the selection;
// disabled is the absence of a message, which is how this crate spells a
// control that cannot be used.
let appearance = column![
Textarea::new(&app.normal)
.placeholder("Normal")
.on_action(Message::Normal),
Textarea::new(&app.readonly)
.readonly(true)
.on_action(Message::Readonly),
Textarea::new(&app.invalid)
.invalid(true)
.on_action(Message::Invalid),
Textarea::new(&app.disabled).placeholder("Disabled"),
]
.spacing(12.0);Unlike disabled, a read-only textarea keeps the normal appearance and still
can be focused, selected and copied, it only rejects the changes made by the
user.
Textarea deliberately does not expose Input-only adornments such as prefix,
suffix, mask toggle, or the clear button. Compose related actions beside the
textarea.
#Atomic inline tokens
// iced's `Content` is one plain string, so a reference cannot live inside it
// as a token that the caret and deletion move as one. This is the Slint
// composer instead: chips on their own line above a borderless field, and
// the text and the references reported apart.
let composer = container(
column![
row(REFERENCES.iter().copied().map(token_chip))
.spacing(6.0)
.align_y(Alignment::Center),
Textarea::new(&app.message)
.appearance(false)
.placeholder("Write a message…")
.height(Length::Fill)
.on_action(Message::Message),
]
.spacing(8.0),
)
.padding(8.0)
.width(Length::Fill)
.height(Length::Fixed(150.0))
.style(|theme: &Theme| {
let tokens = Tokens::of(theme);
container::Style {
background: Some(tokens.background.into()),
border: Border {
color: tokens.input,
width: 1.0,
radius: theme::RADIUS_MD.into(),
},
..container::Style::default()
}
});
let tokens = column![
composer,
muted_line(&format!(
"Text: {}",
if app.message.is_empty() {
"(empty)".to_string()
} else {
app.message.text()
}
)),
muted_line(&format!("References: {}", REFERENCES.join(" · "))),
]
.spacing(10.0);Use tokens to include mentions, file references or commands in a multi-line
message. Insert a token with TextareaState::replace_with_token, or restore a
saved draft with set_value. The default label is ready to use; add a renderer
when you want an icon or other custom content:
Textarea::new(&state)
.token(|token, _, _| InputToken::new(token).icon(IconName::File))VerticalLayout {
spacing: 10px;
alignment: start;
// The frame is the composer's own, which is why the field inside it
// turns its appearance off.
Rectangle {
width: 100%;
height: 150px;
border-width: 1px;
border-color: Theme.input;
border-radius: Theme.radius-md;
background: Theme.background;
VerticalLayout {
padding: 8px;
spacing: 8px;
HorizontalLayout {
spacing: 6px;
alignment: start;
for token[
i
]
in root.references: Rectangle {
border-radius: Theme.radius-sm;
background: Theme.secondary;
HorizontalLayout {
padding-left: 6px;
padding-right: 6px;
padding-top: 3px;
padding-bottom: 3px;
spacing: 4px;
cross-axis-alignment: center;
Icon {
name: "file";
size: 12px;
color: Theme.muted-foreground;
}
Text {
text: token;
color: Theme.foreground;
font-size: Theme.font-size-xs;
vertical-alignment: center;
}
}
}
}
Textarea {
appearance: false;
horizontal-stretch: 1;
placeholder: "Write a message…";
text <=> root.message;
}
}
}
Text {
text: "Text: " + (root.message == "" ? "(empty)" : root.message);
color: Theme.muted-foreground;
font-size: Theme.font-size-xs;
wrap: word-wrap;
}
Text {
text: "References: src/main.rs · tests/dock.rs · Cargo.toml";
color: Theme.muted-foreground;
font-size: Theme.font-size-xs;
wrap: word-wrap;
}
}Import InputToken from gpui_kit::component::input and IconName from
gpui_kit::component. A token wraps onto the next line as a whole, and auto-grow adjusts the textarea
height to fit. Put line breaks in the text between tokens. Use on_token_hover
to show a tooltip or preview without selecting or editing; disabled tokens never
report hover entry; disabling a hovered token sends its exit. See
Input: atomic inline tokens for editing,
activation, draft persistence, mode restrictions and JavaScript APIs.