Text Field
A composition-friendly field with label and validation.
use herogpui::components::input::TextField;TextField on docs.rsSource at v0.14.0
Usage
Showing Usage, live: HeroGPUI compiled to WebAssembly. The canvas has no accessibility tree; each example's description and code below carry the same information.
Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.
use herogpui::prelude::TextField;
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.w(px(256.))
.gap(px(12.))
.children(vec![TextField::new(self.demo_text("tf-usage", "", cx))
.label("Full name")
.placeholder("Ada Lovelace")
.description("As it appears on your ID.")
.into_any_element()])Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.
use herogpui::prelude::{FieldVariant, Surface, TextField};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.w(px(256.))
.gap(px(12.))
.children(vec![Surface::new()
.padding(px(24.))
.gap(px(16.))
.child(
TextField::new(self.demo_text("tf-surface", "", cx))
.label("Full name")
.placeholder("John")
.variant(FieldVariant::Secondary)
.description("Use the secondary variant on a surface"),
)
.into_any_element()])Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.
use herogpui::prelude::TextField;
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.w(px(256.))
.gap(px(12.))
.children(vec![TextField::new(self.demo_text("tf-desc", "", cx))
.label("Full name")
.placeholder("Enter username")
.description("As it appears on your ID.")
.into_any_element()])Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.
use herogpui::prelude::TextField;
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.w(px(256.))
.gap(px(12.))
.children(vec![TextField::new(self.demo_text("tf-req", "", cx))
.label("Full name")
.placeholder("John Doe")
.is_required(true)
.into_any_element()])Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.
use herogpui::prelude::TextField;
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.w(px(256.))
.gap(px(12.))
.children(vec![TextField::new(self.demo_text(
"tf-dis",
"Ada Lovelace",
cx,
))
.label("Full name")
.placeholder("Auto-generated")
.is_disabled(true)
.into_any_element()])Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.
use herogpui::prelude::TextField;
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.items_start()
.justify_center()
.gap(px(12.))
.children(vec![TextField::new(self.demo_text("tf-full", "", cx))
.label("Full name")
.placeholder("John")
.full_width()
.into_any_element()])Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.
use herogpui::prelude::TextField;
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.w(px(256.))
.gap(px(12.))
.children(vec![
TextField::new(self.demo_text("tf-validate", "", cx,))
.label("Full name")
.placeholder("jane_doe")
.is_required(true)
.validate(|value| value
.trim()
.is_empty()
.then(|| "Name is required".into()))
.into_any_element(),
TextField::new(self.demo_text("tf-invalid", "", cx))
.label("Full name")
.placeholder("Ada Lovelace")
// `isInvalid` marks it invalid from the outside, which is
// what a server-side error looks like.
.is_invalid(true)
.error_message("Name is required")
.into_any_element()
])Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.
use herogpui::prelude::{ActiveTheme, TextField};
use herogpui::gpui::prelude::*;
use herogpui::gpui::{App, px};
use herogpui::gpui;
let tf_controlled = self
.demo_text("tf-controlled", "", cx)
.read(cx)
.value()
.to_owned();
gpui::div()
.flex()
.flex_col()
.items_start()
.justify_center()
.gap(px(12.))
.children(vec![
gpui::div()
.w(px(256.))
.flex()
.flex_col()
.child(TextField::new(self.demo_text("tf-controlled", "", cx))
.label("Full name")
.placeholder("Jane")
.on_change(|_, _, _| {}))
.into_any_element(),
para(&format!("Value: {tf_controlled}"), cx),
])Also calls para (included when you copy).
Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.
use herogpui::prelude::TextField;
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
let tf_render_props = self.demo_text("tf-render-props", "Ada", cx);
gpui::div()
.flex()
.flex_col()
.w(px(256.))
.gap(px(12.))
.children(vec![{
let field = tf_render_props.clone();
TextField::new(tf_render_props)
.content(move |state| {
TextField::new(field.clone())
.label(format!(
"{} · {} · {}",
if state.is_required {
"required"
} else {
"optional"
},
if state.is_invalid { "invalid" } else { "valid" },
if state.is_focus_within {
"focused"
} else {
"unfocused"
},
))
.placeholder("Enter your email")
.is_required(true)
.into_any_element()
})
.is_required(true)
.into_any_element()
}])Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.
use herogpui::prelude::TextField;
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.w(px(256.))
.gap(px(12.))
.children(vec![TextField::new(self.demo_text("tf-error", "", cx))
.label("Full name")
.placeholder("Ada Lovelace")
.is_required(true)
.error_message("This field is required.")
.into_any_element()])Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.
use herogpui::prelude::{TextArea, TextField};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.w(px(256.))
.gap(px(12.))
.children(vec![TextArea::new(self.demo_text("tf-area", "", cx))
.label("Bio")
.placeholder("Write your message here...")
.rows(4)
.description("A `TextField` whose input is multi-line")
.into_any_element()])Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.
use herogpui::prelude::{InputType, TextField};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.w(px(256.))
.gap(px(12.))
.children(vec![
TextField::new(self.demo_text("tf-pw", "", cx))
.label("Password")
.placeholder("••••••••")
.input_type(InputType::Password)
.into_any_element(),
TextField::new(self.demo_text("tf-email", "", cx))
.label("Email")
.placeholder("user@example.com")
.input_type(InputType::Email)
.into_any_element(),
])Helpers used by these examples
Plain functions over the public API, defined once here and copied with each example that calls them.
fn para(text: &str, cx: &App) -> gpui::AnyElement {
let colors = cx.colors();
gpui::div()
.text_size(px(14.5))
.line_height(px(24.))
.text_color(colors.foreground)
.max_w(px(720.))
.child(text.to_owned())
.into_any_element()
}Anatomy
Rust types
Text Field is assembled from these builders. The API reference lists each one.
Customization
Shared appearance belongs on ThemeBuilder::components and .recipe("name"). Typed builders change how Text Field looks at one call site. Every builder also carries sx for placement, width and flex: it takes GPUI's own styling methods and refines them over the component's root after the theme, so an instance override wins. Behaviour builders live in the API reference below.
Styling
| Method | Values | Description |
|---|---|---|
full_width() | true | false | Stretches the root and inner control to the available width. |
// The one slot for caller-owned low-level styling: GPUI's
// styling methods, refined over the root element last.
.sx(|el| {
el.bg(gpui::rgba(0xffa500ff)) // background
.text_color(gpui::rgba(0x000000ff)) // text
.w(gpui::px(13.)) // size
.h(gpui::px(12.))
})API reference
Against HeroUI v3.2.6: 19 entries ported, 7 partial, 4 not ported. Partial rows are marked in the tables; entries not ported are listed under each table with the reason.
Builders
| Builder | Values | Default | Description |
|---|---|---|---|
content( | AnyElement | | — | Replacement content receives disabled, invalid, read-only, required and complete focus state. |
full_width( | true | | false | Stretches the root and inner control to the available width. |
new(Partial | string | — | The caller-owned InputState entity supplies stable GPUI identity instead of a element id. Simultaneous fields need distinct InputState owners. |
is_required( | true | | false | Marks the field required for native form validation. |
is_invalid( | true | | — | Forces invalid field chrome and error state. |
validate(Partial | Fn( | — | A callback returns one optional validation message. |
validation_behavior( | ValidationBehavior:: | ValidationBehavior:: | Selects blocking native validation or non-blocking ARIA-style validation. |
validation_errors( | string[] | — | Server messages take precedence over custom validation. |
default_value( | string | — | Seeds the uncontrolled InputState once. |
on_change( | Fn( | — | Reports each accepted edit without duplicate reports for rejected edits. |
is_disabled( | true | | false | Removes focus and editing and applies disabled opacity. |
is_read_only( | true | | false | Keeps focus and selection while suppressing edits. |
name( | string | — | Submission name stored with the current field value. |
auto_focus( | true | | false | Focuses the inner input on its first render. |
focus_ring( | true | | true | GPUI extension: hides only the inner input's visual focus ring while retaining focus and editing. |
Not ported4 HeroUI entries not ported
| HeroUI v3 | Why it is not ported |
|---|---|
TextField.className | Browser styles are unavailable. |
TextField.style | Browser inline styles are unavailable. |
TextField.render | Root-element substitution has no GPUI equivalent. |
TextField.aria-label / aria-labelledby / aria-describedby / aria-details | Gpui-pre 0.3.3 exposes no accessibility attribute tree. |
Parts
| Part | Description |
|---|---|
TextField | Root field state and vertical composition. |
TextFieldPartial | Optional label rendered before the editable control. |
Input | Single-line InputState-backed editable control. |
TextFieldPartial | Multi-line field composition is supplied by the sibling TextArea component. |
TextFieldPartial | Helper message shown while the field is valid. |
TextFieldPartial | Validation or server message replacing the description while invalid. |
States
| State | Builder | Description |
|---|---|---|
Invalid | resolved Input validity | Hides the description and shows the field error. |
Disabled | is_disabled(bool) | Applies disabled opacity and removes focus and editing. |
Focus within | TextFieldRenderState::is_focus_within | Replacement content receives focus-within while the inner input owns focus. |
Focus visible | TextFieldRenderState::is_focus_visible | Replacement content receives keyboard-visible focus state. |