Skip to content

SearchField

A search input, cleared by Escape or its button and submitted by Enter.

Solidtype_heroui.Search_field is Kobalte’s TextField.Root, with the classes HeroUI’s own searchFieldVariants gives, whose input is a search: Escape clears it, Enter submits it, and its clear button clears it and gives its input the focus back, as React Aria’s does. Kobalte’s Search is a combobox; HeroUI’s search field is a text field, which this is.

HeroUI’s examples, with HeroUI’s icons from @gravity-ui/icons.

search_field_usage.mlx
module Label = Solidtype_heroui.Label
module Search_field = Solidtype_heroui.Search_field
let make () =
<Search_field name="search">
<Label>"Search"</Label>
<Search_field.Group>
<Search_field.Search_icon />
<Search_field.Input class_="w-[280px]" placeholder="Search..." />
<Search_field.Clear_button />
</Search_field.Group>
</Search_field>
<Search_field>
<Label>"Search"</Label>
<Search_field.Group>
<Search_field.Search_icon />
<Search_field.Input placeholder="Search..." />
<Search_field.Clear_button />
</Search_field.Group>
<Description>"Find a task"</Description>
</Search_field>
search_field_variants.mlx
module Label = Solidtype_heroui.Label
module Search_field = Solidtype_heroui.Search_field
let make () =
<div class_="flex flex-col gap-4">
<Search_field name="primary-search" variant=`primary>
<Label>"Primary variant"</Label>
<Search_field.Group>
<Search_field.Search_icon />
<Search_field.Input class_="w-[280px]" placeholder="Search..." />
<Search_field.Clear_button />
</Search_field.Group>
</Search_field>
<Search_field name="secondary-search" variant=`secondary>
<Label>"Secondary variant"</Label>
<Search_field.Group>
<Search_field.Search_icon />
<Search_field.Input class_="w-[280px]" placeholder="Search..." />
<Search_field.Clear_button />
</Search_field.Group>
</Search_field>
</div>
Enter keywords to search
Use filters to refine your search
search_field_surface.mlx
module Description = Solidtype_heroui.Description
module Label = Solidtype_heroui.Label
module Search_field = Solidtype_heroui.Search_field
module Surface = Solidtype_heroui.Surface
let make () =
<Surface class_="flex w-full max-w-sm flex-col gap-4 rounded-3xl p-6">
<Search_field name="search" variant=`secondary>
<Label>"Search"</Label>
<Search_field.Group>
<Search_field.Search_icon />
<Search_field.Input class_="w-full" placeholder="Search..." />
<Search_field.Clear_button />
</Search_field.Group>
<Description>"Enter keywords to search"</Description>
</Search_field>
<Search_field name="search-2" variant=`secondary>
<Label>"Advanced search"</Label>
<Search_field.Group>
<Search_field.Search_icon />
<Search_field.Input class_="w-full" placeholder="Advanced search..." />
<Search_field.Clear_button />
</Search_field.Group>
<Description>"Use filters to refine your search"</Description>
</Search_field>
</Surface>
Enter keywords to search for products
Search by name, email, or username
search_field_description.mlx
module Description = Solidtype_heroui.Description
module Label = Solidtype_heroui.Label
module Search_field = Solidtype_heroui.Search_field
let make () =
<div class_="flex flex-col gap-4">
<Search_field name="search">
<Label>"Search products"</Label>
<Search_field.Group>
<Search_field.Search_icon />
<Search_field.Input class_="w-[280px]" placeholder="Search products..."
/>
<Search_field.Clear_button />
</Search_field.Group>
<Description>"Enter keywords to search for products"</Description>
</Search_field>
<Search_field name="search-users">
<Label>"Search users"</Label>
<Search_field.Group>
<Search_field.Search_icon />
<Search_field.Input class_="w-[280px]" placeholder="Search users..." />
<Search_field.Clear_button />
</Search_field.Group>
<Description>"Search by name, email, or username"</Description>
</Search_field>
</div>
Minimum 3 characters required
search_field_required.mlx
module Description = Solidtype_heroui.Description
module Label = Solidtype_heroui.Label
module Search_field = Solidtype_heroui.Search_field
let make () =
<div class_="flex flex-col gap-4">
<Search_field is_required=true name="search">
<Label>"Search"</Label>
<Search_field.Group>
<Search_field.Search_icon />
<Search_field.Input class_="w-[280px]" placeholder="Search..." />
<Search_field.Clear_button />
</Search_field.Group>
</Search_field>
<Search_field is_required=true name="search-query">
<Label>"Search query"</Label>
<Search_field.Group>
<Search_field.Search_icon />
<Search_field.Input
class_="w-[280px]" placeholder="Enter search query..."
/>
<Search_field.Clear_button />
</Search_field.Group>
<Description>"Minimum 3 characters required"</Description>
</Search_field>
</div>
This search field is disabled
This search field is disabled
search_field_disabled.mlx
module Description = Solidtype_heroui.Description
module Label = Solidtype_heroui.Label
module Search_field = Solidtype_heroui.Search_field
let make () =
<div class_="flex flex-col gap-4">
<Search_field is_disabled=true name="search" value="Disabled search">
<Label>"Search"</Label>
<Search_field.Group>
<Search_field.Search_icon />
<Search_field.Input class_="w-[280px]" placeholder="Search..." />
<Search_field.Clear_button />
</Search_field.Group>
<Description>"This search field is disabled"</Description>
</Search_field>
<Search_field is_disabled=true name="search-empty">
<Label>"Search"</Label>
<Search_field.Group>
<Search_field.Search_icon />
<Search_field.Input class_="w-[280px]" placeholder="Search..." />
<Search_field.Clear_button />
</Search_field.Group>
<Description>"This search field is disabled"</Description>
</Search_field>
</div>
search_field_full_width.mlx
module Label = Solidtype_heroui.Label
module Search_field = Solidtype_heroui.Search_field
let make () =
<div class_="w-[400px] space-y-4">
<Search_field full_width=true name="search">
<Label>"Search"</Label>
<Search_field.Group>
<Search_field.Search_icon />
<Search_field.Input placeholder="Search..." />
<Search_field.Clear_button />
</Search_field.Group>
</Search_field>
</div>
Search query must be at least 3 characters
Invalid characters in search query
search_field_validation.mlx
module Field_error = Solidtype_heroui.Field_error
module Label = Solidtype_heroui.Label
module Search_field = Solidtype_heroui.Search_field
let make () =
<div class_="flex flex-col gap-4">
<Search_field is_invalid=true is_required=true name="search" value="ab">
<Label>"Search"</Label>
<Search_field.Group>
<Search_field.Search_icon />
<Search_field.Input class_="w-[280px]" placeholder="Search..." />
<Search_field.Clear_button />
</Search_field.Group>
<Field_error>"Search query must be at least 3 characters"</Field_error>
</Search_field>
<Search_field is_invalid=true name="search-invalid" value="invalid@query">
<Label>"Search"</Label>
<Search_field.Group>
<Search_field.Search_icon />
<Search_field.Input class_="w-[280px]" placeholder="Search..." />
<Search_field.Clear_button />
</Search_field.Group>
<Field_error>"Invalid characters in search query"</Field_error>
</Search_field>
</div>
Current value: (empty)
search_field_controlled.mlx
module S = Solidtype.Solid
module Jsx = Solidtype.Jsx
module Button = Solidtype_heroui.Button
module Description = Solidtype_heroui.Description
module Label = Solidtype_heroui.Label
module Search_field = Solidtype_heroui.Search_field
let make () =
let value, set_value = S.create_signal "" in
<div class_="flex flex-col gap-4">
<Search_field
name="search" value=(fun () -> S.get value) on_change=(S.set set_value)>
<Label>"Search"</Label>
<Search_field.Group>
<Search_field.Search_icon />
<Search_field.Input class_="w-[280px]" placeholder="Search..." />
<Search_field.Clear_button />
</Search_field.Group>
<Description>
(fun () ->
Jsx.string
("Current value: "
^ match S.get value with "" -> "(empty)" | v -> v))
</Description>
</Search_field>
<div class_="flex gap-2">
<Button variant=`tertiary on_click=(fun _ -> S.set set_value "")>
"Clear"
</Button>
<Button
variant=`tertiary on_click=(fun _ -> S.set set_value "example query")>
"Set example"
</Button>
</div>
</div>

HeroUI’s button takes isPending while the search is sent; the port’s button has no pending state yet, so this one shows the spinner alone.

Enter at least 3 characters to search
search_field_form.mlx
module S = Solidtype.Solid
module Jsx = Solidtype.Jsx
module Show = Solidtype.Show
module Button = Solidtype_heroui.Button
module Description = Solidtype_heroui.Description
module Field_error = Solidtype_heroui.Field_error
module Form = Solidtype_heroui.Form
module Label = Solidtype_heroui.Label
module Search_field = Solidtype_heroui.Search_field
module Spinner = Solidtype_heroui.Spinner
external prevent_default : Dom.event -> unit = "preventDefault" [@@mel.send]
let min_length = 3
let make () =
let value, set_value = S.create_signal "" in
let is_submitting, set_is_submitting = S.create_signal false in
let is_invalid () =
let n = String.length (S.get value) in
n > 0 && n < min_length
in
let handle_submit e =
prevent_default e;
if String.length (S.get value) >= min_length then (
S.set set_is_submitting true;
(* Simulate API call *)
ignore
(Js.Global.setTimeout
~f:(fun () ->
Js.log2 "Search submitted, query:" (S.get value);
S.set set_value "";
S.set set_is_submitting false)
1500
: Js.Global.timeoutId))
in
<Form class_="flex w-[280px] flex-col gap-4" on_submit=handle_submit>
<Search_field
is_required=true
is_invalid=(fun () -> is_invalid ())
name="search"
value=(fun () -> S.get value)
on_change=(S.set set_value)>
<Label>"Search products"</Label>
<Search_field.Group>
<Search_field.Search_icon />
<Search_field.Input class_="w-full" placeholder="Search products..." />
<Search_field.Clear_button />
</Search_field.Group>
<Show
when_=(fun () -> is_invalid ())
fallback=(<Description>
(fun () ->
Jsx.string
("Enter at least " ^ string_of_int min_length
^ " characters to search"))
</Description>)>
<Field_error>
(fun () ->
Jsx.string
("Search query must be at least " ^ string_of_int min_length
^ " characters"))
</Field_error>
</Show>
</Search_field>
<Button
class_="w-full"
is_disabled=(fun () -> String.length (S.get value) < min_length)
type_=`submit
variant=`primary>
<Show when_=(fun () -> not (S.get is_submitting))>"Search"</Show>
<Show when_=(fun () -> S.get is_submitting)>
<Spinner color=`current size=`sm /> "Searching..."
</Show>
</Button>
</Form>
Enter at least 3 characters to search
search_field_with_validation.mlx
module S = Solidtype.Solid
module Show = Solidtype.Show
module Description = Solidtype_heroui.Description
module Field_error = Solidtype_heroui.Field_error
module Label = Solidtype_heroui.Label
module Search_field = Solidtype_heroui.Search_field
let make () =
let value, set_value = S.create_signal "" in
let is_invalid () =
let n = String.length (S.get value) in
n > 0 && n < 3
in
<div class_="flex flex-col gap-4">
<Search_field
is_required=true
is_invalid=(fun () -> is_invalid ())
name="search"
value=(fun () -> S.get value)
on_change=(S.set set_value)>
<Label>"Search"</Label>
<Search_field.Group>
<Search_field.Search_icon />
<Search_field.Input class_="w-[280px]" placeholder="Search..." />
<Search_field.Clear_button />
</Search_field.Group>
<Show
when_=(fun () -> is_invalid ())
fallback=(<Description>
"Enter at least 3 characters to search"
</Description>)>
<Field_error>"Search query must be at least 3 characters"</Field_error>
</Show>
</Search_field>
</div>
Custom icon children
search_field_custom_icons.mlx
module Description = Solidtype_heroui.Description
module Label = Solidtype_heroui.Label
module Search_field = Solidtype_heroui.Search_field
let make () =
<div class_="flex flex-col gap-4">
<Search_field name="search-custom">
<Label>"Search (Custom Icons)"</Label>
<Search_field.Group>
<Search_field.Search_icon><Gravity.Funnel /></Search_field.Search_icon>
<Search_field.Input class_="w-[280px]" placeholder="Search..." />
<Search_field.Clear_button>
<Gravity.Circle_xmark_fill />
</Search_field.Clear_button>
</Search_field.Group>
<Description>"Custom icon children"</Description>
</Search_field>
</div>
Use keyboard shortcut to quickly focus this field
Press⇧Sto focus the search field
search_field_keyboard_shortcut.mlx
module S = Solidtype.Solid
module Description = Solidtype_heroui.Description
module Kbd = Solidtype_heroui.Kbd
module Label = Solidtype_heroui.Label
module Search_field = Solidtype_heroui.Search_field
type window
external window : window = "window"
external add_key_down :
window -> (_[@mel.as "keydown"]) -> ((Dom.keyboardEvent -> unit)[@u]) -> unit
= "addEventListener"
[@@mel.send]
external remove_key_down :
window -> (_[@mel.as "keydown"]) -> ((Dom.keyboardEvent -> unit)[@u]) -> unit
= "removeEventListener"
[@@mel.send]
external key : Dom.keyboardEvent -> string = "key" [@@mel.get]
external shift_key : Dom.keyboardEvent -> bool = "shiftKey" [@@mel.get]
external meta_key : Dom.keyboardEvent -> bool = "metaKey" [@@mel.get]
external ctrl_key : Dom.keyboardEvent -> bool = "ctrlKey" [@@mel.get]
external alt_key : Dom.keyboardEvent -> bool = "altKey" [@@mel.get]
external prevent_default : Dom.keyboardEvent -> unit = "preventDefault"
[@@mel.send]
external query : Dom.htmlDivElement -> string -> Dom.htmlElement Js.null
= "querySelector"
[@@mel.send]
external active_element : Dom.htmlElement Js.null = "activeElement"
[@@mel.scope "document"]
external focus : Dom.htmlElement -> unit = "focus" [@@mel.send]
external blur : Dom.htmlElement -> unit = "blur" [@@mel.send]
let make () =
let value, set_value = S.create_signal "" in
(* The element around the field, whose input a key focuses. *)
let around = ref None in
let handle_key_down =
fun[@u] e ->
let input =
Option.bind !around (fun d -> Js.Null.toOption (query d "input"))
in
(* Check for Shift+S *)
if
shift_key e
&& String.equal (key e) "S"
&& not (meta_key e || ctrl_key e || alt_key e)
then (
prevent_default e;
Option.iter focus input);
(* Check for ESC key to blur the input *)
match (key e, input, Js.Null.toOption active_element) with
| "Escape", Some i, Some active when i == active -> blur i
| _ -> ()
in
S.on_mount (fun () ->
add_key_down window handle_key_down;
S.on_cleanup (fun () -> remove_key_down window handle_key_down));
<div class_="flex flex-col gap-4">
<div ref=(fun d -> around := Some d)>
<Search_field
name="search" value=(fun () -> S.get value) on_change=(S.set set_value)>
<Label>"Search"</Label>
<Search_field.Group>
<Search_field.Search_icon />
<Search_field.Input class_="w-[280px]" placeholder="Search..." />
<Search_field.Clear_button />
</Search_field.Group>
<Description>
"Use keyboard shortcut to quickly focus this field"
</Description>
</Search_field>
</div>
<div class_="text-default-500 flex items-center gap-2 text-sm">
<span>"Press"</span>
<Kbd><Kbd.Abbr key_value=`shift /> <Kbd.Content>"S"</Kbd.Content></Kbd>
<span>"to focus the search field"</span>
</div>
</div>

HeroUI’s Render Function example gives the field render, React’s way to swap the element it renders, which a Solid component has no need of.

class_ is added after HeroUI’s classes.

search_field_tailwind.mlx
module Label = Solidtype_heroui.Label
module Search_field = Solidtype_heroui.Search_field
let make () =
<Search_field class_="w-full max-w-64" name="docs" variant=`secondary>
<Label class_="font-medium text-foreground">"Search docs"</Label>
<Search_field.Group class_="rounded-xl bg-default">
<Search_field.Search_icon class_="text-muted" />
<Search_field.Input
class_="placeholder:text-muted" placeholder="Components, guides..."
/>
<Search_field.Clear_button class_="text-muted" />
</Search_field.Group>
</Search_field>
  • .search-field, and .search-field--primary, .search-field--secondary, .search-field--full-width
  • .search-field__group, .search-field__search-icon, .search-field__input, .search-field__clear-button
  • Empty: [data-empty="true"], which hides the clear button
  • Hover, Focus within, Focus visible: on the group
  • Disabled: [data-disabled="true"]
  • Invalid: [data-invalid="true"]

Each prop beside the name HeroUI’s React component gives it.

React solidtype Type Default Description
variant variant [ `primary | `secondary ] `primary Its look
fullWidth full_width bool false Whether it takes its container’s width
value value string Its query, controlled
defaultValue default_value string "" Its query at first, kept by itself
onChange on_change string -> unit Told each change of its query
onSubmit on_submit string -> unit Told its query as Enter submits it
onClear on_clear unit -> unit Told as it is cleared
isDisabled is_disabled bool Whether it is disabled, else as its fieldset is
isInvalid is_invalid bool false Whether it is invalid
isReadOnly is_read_only bool false Whether its query is kept
isRequired is_required bool false Whether it is required
name name string Its name in a form
aria-label aria_label string Its name where no label names it
className class_ string Added after HeroUI’s classes
onBlur on_blur Dom.focusEvent -> unit Called once focus leaves it for an element outside it

SearchField.Group, SearchIcon, Input, ClearButton

Section titled “SearchField.Group, SearchIcon, Input, ClearButton”

Each takes class_, added after HeroUI’s class; the input a placeholder; the icon children, drawn in place of HeroUI’s magnifier.

Original HeroUI documentation: SearchField.

The same HeroUI SearchField twice, under the same props: in solidtype on the left, in React on the right. Below them, every attribute of the two groups: a marked row is one they disagree on.

solidtype — OCaml, on Solid and Kobalte
React — TypeScript, on React Aria
attributesolidtypeReact