Search Field
A text input specialised for search, with a clear action.
use herogpui::components::input::SearchField;SearchField 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::{ActiveTheme, SearchField};
use herogpui::gpui::prelude::*;
use herogpui::gpui::{App, px};
use herogpui::gpui;
let query = self.search_query.clone();
gpui::div()
.flex()
.flex_col()
.items_start()
.justify_center()
.gap(px(12.))
.children(vec![
gpui::div()
.w(px(256.))
.flex()
.flex_col()
.child(SearchField::new(self.search_state.clone())
.label("Search docs")
.placeholder("Search components")
.on_change(cx.listener(|this, text: &str, _, cx| {
this.search_query = text.to_owned();
cx.notify();
})))
.into_any_element(),
para(
&if query.is_empty() {
"Type to search".to_owned()
} else {
format!("Query: {query}")
},
cx,
),
])Also calls para (included when you copy).
`height`, `padding_x` and `is_bare` reach the inner field; a 28px bare search box. Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.
use herogpui::prelude::SearchField;
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.w(px(256.))
.gap(px(12.))
.children(vec![SearchField::new(self.demo_text("sf-custom-box", "", cx))
.label("Compact")
.placeholder("Search...")
.height(px(28.))
.padding_x(px(8.))
.is_bare(true)
.font_family(MONO_FONT)
.into_any_element()])Also calls MONO_FONT (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::{FieldVariant, SearchField};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
gpui::div()
.flex()
.flex_col()
.w(px(256.))
.gap(px(12.))
.children(vec![
SearchField::new(self.demo_text("sf-v-primary", "", cx))
.label("Primary")
.placeholder("Search...")
.into_any_element(),
SearchField::new(self.demo_text("sf-v-secondary", "", cx))
.label("Secondary")
.placeholder("Search...")
.variant(FieldVariant::Secondary)
.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, SearchField, Surface};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
let surface = self.demo_text("sf-surface", "", cx);
gpui::div()
.flex()
.flex_col()
.w(px(256.))
.gap(px(12.))
.children(vec![Surface::new()
.padding(px(24.))
.gap(px(16.))
.child(
SearchField::new(surface)
.label("Search")
.placeholder("Search...")
.variant(FieldVariant::Secondary)
.description("Enter keywords to search"),
)
.into_any_element()])Labels use 14px text with 20px lines; helper and error text use 12px text with 16px lines. Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.
use herogpui::prelude::SearchField;
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
let described = self.demo_text("sf-desc", "", cx);
gpui::div()
.flex()
.flex_col()
.w(px(256.))
.gap(px(12.))
.children(vec![SearchField::new(described)
.label("Search")
.placeholder("Search products...")
.description("Searches titles and body text")
.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::SearchField;
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
let required = self.demo_text("sf-required", "", cx);
gpui::div()
.flex()
.flex_col()
.w(px(256.))
.gap(px(12.))
.children(vec![SearchField::new(required)
.label("Search")
.placeholder("Enter search query...")
.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::SearchField;
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
let disabled = self.demo_text("sf-disabled", "Read-only query", cx);
gpui::div()
.flex()
.flex_col()
.w(px(256.))
.gap(px(12.))
.children(vec![SearchField::new(disabled)
.label("Search")
.placeholder("Search...")
.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::SearchField;
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
let full = self.demo_text("sf-full", "", cx);
gpui::div()
.flex()
.flex_col()
.items_start()
.justify_center()
.gap(px(12.))
.children(vec![SearchField::new(full)
.label("Search")
.placeholder("Search...")
.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::SearchField;
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
let invalid = self.demo_text("sf-invalid", "ab", cx);
gpui::div()
.flex()
.flex_col()
.w(px(256.))
.gap(px(12.))
.children(vec![SearchField::new(invalid)
.label("Search")
.placeholder("Search...")
.is_required(true)
.is_invalid(true)
.validation_errors(["Search query must be at least 3 characters"])
.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, SearchField};
use herogpui::gpui::prelude::*;
use herogpui::gpui::{App, px};
use herogpui::gpui;
let controlled = self.demo_text("sf-controlled", "", cx);
let controlled_text = controlled.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(SearchField::new(controlled)
.label("Search")
.placeholder("Search...")
.on_change(|_, _, _| {})
// Enter submits: v3's `onSubmit`.
.on_submit(cx.listener(|this, text: &str, _, cx| {
this.search_query = text.to_owned();
cx.notify();
})))
.into_any_element(),
para(
&if controlled_text.is_empty() {
"Empty".to_owned()
} else {
format!("Value: {controlled_text}")
},
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::SearchField;
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
let render_props = self.demo_text("sf-render-props", "hero", cx);
gpui::div()
.flex()
.flex_col()
.w(px(256.))
.gap(px(12.))
.children(vec![{
let parts = render_props.clone();
SearchField::new(render_props)
.content(move |state| {
SearchField::new(parts.clone())
.label(format!(
"{} · {} · {}",
if state.is_empty { "empty" } else { "has value" },
if state.is_focus_within {
"focused"
} else {
"unfocused"
},
state.value
))
.placeholder("Search")
.into_any_element()
})
.into_any_element()
}])`validate` is run by the component: it returns the message, and the field shows it. Type one or two characters. Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.
use herogpui::prelude::SearchField;
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
let validated = self.demo_text("sf-validated", "", cx);
gpui::div()
.flex()
.flex_col()
.items_start()
.justify_center()
.gap(px(12.))
.children(vec![
gpui::div()
.w(px(256.))
.flex()
.flex_col()
.child(SearchField::new(validated)
.label("Search")
.placeholder("Search...")
.is_required(true)
.description("Enter at least 3 characters to search")
.validate(|value| {
(!value.is_empty() && value.chars().count() < 3).then(|| {
"Search query must be at least 3 characters".into()
})
}))
.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::{Button, Form, FormField, SearchField};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
let form_field = self.demo_text("sf-form", "", cx);
gpui::div()
.flex()
.flex_col()
.w(px(256.))
.gap(px(12.))
.children(vec![{
let field = SearchField::new(form_field)
.label("Search")
.placeholder("Search products...")
.name("query")
.is_required(true)
.validate(|value| {
(value.chars().count() < 3)
.then(|| "Enter at least 3 characters".into())
});
Form::new()
.field(FormField::text(self.demo_text("sf-form", "", cx)))
.child(field)
.child(Button::new("search-field-form-submit").label("Search"))
.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, SearchField, icons};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
let icons = self.demo_text("sf-icons", "HeroGPUI", cx);
gpui::div()
.flex()
.flex_col()
.w(px(256.))
.gap(px(12.))
.children(vec![SearchField::new(icons)
.label("Search")
.placeholder("Search...")
.search_icon(gpui::svg()
.size(px(16.))
.path(icons::GLOBE)
.text_color(cx.colors().foreground)
.into_any_element())
.clear_icon(gpui::svg()
.size(px(16.))
.path(icons::CHECK)
.text_color(cx.colors().foreground)
.into_any_element())
.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::{Kbd, SearchField};
use herogpui::gpui::prelude::*;
use herogpui::gpui::px;
use herogpui::gpui;
let shortcut = self.demo_text("sf-shortcut", "", cx);
gpui::div()
.flex()
.flex_col()
.w(px(256.))
.gap(px(12.))
.children(vec![SearchField::new(shortcut)
.label("Search")
.placeholder("Search...")
.end_content(Kbd::new().child("Shift S"))
.description("Press Shift+S to focus")
.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()
}const MONO_FONT: &str = if cfg!(target_arch = "wasm32") {
// The web shell bundles JetBrains Mono; the literal family "monospace"
// resolves to nothing there and panics text resolution.
"JetBrains Mono"
} else if cfg!(target_os = "macos") {
"Menlo"
} else {
"Consolas"
};Anatomy
Rust types
Search 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 Search 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 field to the available width. |
variant(FieldVariant) | FieldVariant::Primary | FieldVariant::Secondary | Primary field chrome or the lower-emphasis surface variant. |
// 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: 28 entries ported, 8 partial, 9 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, focus, value and empty state. |
full_width( | true | | false | Stretches the root and field to the available width. |
new(Partial | string | — | The caller-owned InputState entity supplies stable GPUI identity instead of a element id. |
variant( | FieldVariant:: | FieldVariant:: | Primary field chrome or the lower-emphasis surface variant. |
default_value( | string | — | Seeds the uncontrolled InputState once. |
on_change( | Fn( | — | Reports each accepted query edit. |
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. |
is_disabled( | true | | false | Removes focus, editing and clear actions and applies disabled opacity. |
is_read_only( | true | | false | Keeps focus and selection while suppressing edits and clear actions. |
name( | string | — | Submission name stored with the current query. |
auto_focus( | true | | false | Focuses the input on its first render. |
focus_ring( | true | | true | GPUI extension: hides only the input's visual focus ring while retaining focus, editing and clear behavior. |
on_submit( | Fn( | — | Enter reports the current query. |
on_clear( | Fn( | — | The clear button and Escape report an explicit clear; deleting the final character does not. |
placeholder( | string | — | Placeholder displayed while the query is empty and unfocused. |
search_icon( | AnyElement | AnyElement:: | Replaces the leading magnifier. |
clear_icon( | AnyElement | AnyElement:: | Replaces the glyph while preserving the built-in clear action and geometry. |
Not ported9 HeroUI entries not ported
| HeroUI v3 | Why it is not ported |
|---|---|
SearchField.className | Browser styles are unavailable. |
SearchField.style | Browser inline styles are unavailable. |
SearchField.render | Root-element substitution has no GPUI equivalent. |
SearchField.aria-label / aria-labelledby / aria-describedby / aria-details | Gpui-pre 0.3.3 exposes no accessibility attribute tree. |
SearchField.Group.className | Arbitrary group classes are unavailable. |
SearchField.Input.className | Arbitrary input classes are unavailable. |
SearchField.Input.type | SearchField has one fixed input mode, so there is no selectable type builder. |
SearchField.SearchIcon.className | Arbitrary icon classes are unavailable. |
SearchField.ClearButton.className | Arbitrary clear-button classes are unavailable. |
Parts
| Part | Description |
|---|---|
SearchField | Root field state, label, description and error composition. |
SearchFieldPartial | Unified field shell containing icon, editable query and clear affordance. |
InputPartial | InputState-backed editable search query. |
SearchField | Built-in or caller-supplied leading icon. |
Input | Clear action with built-in or caller-supplied glyph, shown only for an editable non-empty query. |
States
| State | Builder | Description |
|---|---|---|
Invalid | resolved Input validity | Hides description and applies invalid group chrome. |
Disabled | is_disabled(bool) | Applies disabled opacity and removes focus, editing and clear actions. |
Focus within | Input focus | Draws focused field chrome while the input owns focus. |
Focus visible | SearchFieldRenderState::is_focus_visible | Replacement content receives keyboard-visible focus state; built-in chrome uses focus-within. |
Hovered | Input hover | Uses hover fill and border while focus is outside the group. |
Empty | InputState::is_empty | Removes the clear affordance from hit testing and painting. |