Skip to content
PorabuildHeroGPUI component library
v0.14.0 · Apache-2.0

Forms

Search Field

A text input specialised for search, with a clear action.

Rust
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.

Usage

Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.

Rust
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).

Box Customisation

`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>.

Rust
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).

Variants

Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.

Rust
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(),
    ])

In Surface

Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.

Rust
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()])

With Description

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>.

Rust
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()])

Required Field

Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.

Rust
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()])

Disabled State

Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.

Rust
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()])

Full Width

Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.

Rust
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()])

Validation

Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.

Rust
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()])

Controlled

Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.

Rust
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).

Render Props

Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.

Rust
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()
    }])

With Validation

`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>.

Rust
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(),
    ])

Form Example

Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.

Rust
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()
    }])

Custom Icons

Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.

Rust
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()])

With Keyboard Shortcut

Runs in a view's render: self is the view holding this example's state and cx is its Context<Self>.

Rust
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.

para
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()
}
MONO_FONT
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

SearchFieldInput

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

Search Field styling
MethodValuesDescription
full_width()true | falseStretches the root and field to the available width.
variant(FieldVariant)FieldVariant::Primary | FieldVariant::SecondaryPrimary field chrome or the lower-emphasis surface variant.
Rust
// 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

Search Field builders
BuilderValuesDefaultDescription
content(render)AnyElement | Fn(values: SearchFieldRenderProps) -> AnyElement—Replacement content receives disabled, invalid, read-only, required, focus, value and empty state.
full_width()true | falsefalseStretches the root and field to the available width.
new(state)Partialstring—The caller-owned InputState entity supplies stable GPUI identity instead of a element id.
variant(FieldVariant)FieldVariant::Primary | FieldVariant::SecondaryFieldVariant::PrimaryPrimary field chrome or the lower-emphasis surface variant.
default_value(text)string—Seeds the uncontrolled InputState once.
on_change(callback)Fn(value: string) -> ()—Reports each accepted query edit.
is_required(bool)true | falsefalseMarks the field required for native form validation.
is_invalid(bool)true | false—Forces invalid field chrome and error state.
validate(callback)PartialFn(value: string) -> ValidationError | true | null—A callback returns one optional validation message.
validation_behavior(ValidationBehavior)ValidationBehavior::Native | ValidationBehavior::AriaValidationBehavior::NativeSelects blocking native validation or non-blocking ARIA-style validation.
validation_errors(errors)string[]—Server messages take precedence over custom validation.
is_disabled(bool)true | falsefalseRemoves focus, editing and clear actions and applies disabled opacity.
is_read_only(bool)true | falsefalseKeeps focus and selection while suppressing edits and clear actions.
name(text)string—Submission name stored with the current query.
auto_focus(bool)true | falsefalseFocuses the input on its first render.
focus_ring(bool)true | falsetrueGPUI extension: hides only the input's visual focus ring while retaining focus, editing and clear behavior.
on_submit(callback)Fn(value: string) -> ()—Enter reports the current query.
on_clear(callback)Fn() -> ()—The clear button and Escape report an explicit clear; deleting the final character does not.
placeholder(text)string—Placeholder displayed while the query is empty and unfocused.
search_icon(element)AnyElementAnyElement::SearchIconReplaces the leading magnifier.
clear_icon(element)AnyElementAnyElement::CloseIconReplaces the glyph while preserving the built-in clear action and geometry.
Not ported9 HeroUI entries not ported
Search Field builders not ported
HeroUI v3Why it is not ported
SearchField.classNameBrowser styles are unavailable.
SearchField.styleBrowser inline styles are unavailable.
SearchField.renderRoot-element substitution has no GPUI equivalent.
SearchField.aria-label / aria-labelledby / aria-describedby / aria-detailsGpui-pre 0.3.3 exposes no accessibility attribute tree.
SearchField.Group.classNameArbitrary group classes are unavailable.
SearchField.Input.classNameArbitrary input classes are unavailable.
SearchField.Input.typeSearchField has one fixed input mode, so there is no selectable type builder.
SearchField.SearchIcon.classNameArbitrary icon classes are unavailable.
SearchField.ClearButton.classNameArbitrary clear-button classes are unavailable.

Parts

Search Field parts
PartDescription
SearchFieldRoot field state, label, description and error composition.
SearchFieldPartialUnified field shell containing icon, editable query and clear affordance.
InputPartialInputState-backed editable search query.
SearchFieldBuilt-in or caller-supplied leading icon.
InputClear action with built-in or caller-supplied glyph, shown only for an editable non-empty query.

States

Search Field states
StateBuilderDescription
Invalidresolved Input validityHides description and applies invalid group chrome.
Disabledis_disabled(bool)Applies disabled opacity and removes focus, editing and clear actions.
Focus withinInput focusDraws focused field chrome while the input owns focus.
Focus visibleSearchFieldRenderState::is_focus_visibleReplacement content receives keyboard-visible focus state; built-in chrome uses focus-within.
HoveredInput hoverUses hover fill and border while focus is outside the group.
EmptyInputState::is_emptyRemoves the clear affordance from hit testing and painting.