Search input field with clear button and search icon
Import
import { SearchField } from "heroui-solid";Usage
import { Label, SearchField } from "heroui-solid"
export function Basic() {
return (
<SearchField name="search">
<Label>Search</Label>
<SearchField.Group>
<SearchField.SearchIcon />
<SearchField.Input class="w-[280px]" placeholder="Search..." />
<SearchField.ClearButton />
</SearchField.Group>
</SearchField>
)
}Anatomy
import { SearchField, Label, Description, FieldError } from "heroui-solid";
export default () => (
<SearchField>
<Label />
<SearchField.Group>
<SearchField.SearchIcon />
<SearchField.Input />
<SearchField.ClearButton />
</SearchField.Group>
<Description />
<FieldError />
</SearchField>
);SearchField allows users to enter and clear a search query. It includes a search icon and an optional clear button for easy reset.
With Description
import { Description, Label, SearchField } from "heroui-solid"
export function WithDescription() {
return (
<div class="flex flex-col gap-4">
<SearchField name="search">
<Label>Search products</Label>
<SearchField.Group>
<SearchField.SearchIcon />
<SearchField.Input
class="w-[280px]"
placeholder="Search products..."
/>
<SearchField.ClearButton />
</SearchField.Group>
<Description>Enter keywords to search for products</Description>
</SearchField>
<SearchField name="search-users">
<Label>Search users</Label>
<SearchField.Group>
<SearchField.SearchIcon />
<SearchField.Input class="w-[280px]" placeholder="Search users..." />
<SearchField.ClearButton />
</SearchField.Group>
<Description>Search by name, email, or username</Description>
</SearchField>
</div>
)
}Required Field
import { Description, Label, SearchField } from "heroui-solid"
export function Required() {
return (
<div class="flex flex-col gap-4">
<SearchField isRequired name="search">
<Label>Search</Label>
<SearchField.Group>
<SearchField.SearchIcon />
<SearchField.Input class="w-[280px]" placeholder="Search..." />
<SearchField.ClearButton />
</SearchField.Group>
</SearchField>
<SearchField isRequired name="search-query">
<Label>Search query</Label>
<SearchField.Group>
<SearchField.SearchIcon />
<SearchField.Input
class="w-[280px]"
placeholder="Enter search query..."
/>
<SearchField.ClearButton />
</SearchField.Group>
<Description>Minimum 3 characters required</Description>
</SearchField>
</div>
)
}Validation
Use isInvalid together with FieldError to surface validation messages.
import { FieldError, Label, SearchField } from "heroui-solid"
export function Validation() {
return (
<div class="flex flex-col gap-4">
<SearchField isInvalid isRequired name="search" value="ab">
<Label>Search</Label>
<SearchField.Group>
<SearchField.SearchIcon />
<SearchField.Input class="w-[280px]" placeholder="Search..." />
<SearchField.ClearButton />
</SearchField.Group>
<FieldError>Search query must be at least 3 characters</FieldError>
</SearchField>
<SearchField isInvalid name="search-invalid">
<Label>Search</Label>
<SearchField.Group>
<SearchField.SearchIcon />
<SearchField.Input
class="w-[280px]"
placeholder="Search..."
value="invalid@query"
/>
<SearchField.ClearButton />
</SearchField.Group>
<FieldError>Invalid characters in search query</FieldError>
</SearchField>
</div>
)
}Disabled State
import { Description, Label, SearchField } from "heroui-solid"
export function Disabled() {
return (
<div class="flex flex-col gap-4">
<SearchField isDisabled name="search" value="Disabled search">
<Label>Search</Label>
<SearchField.Group>
<SearchField.SearchIcon />
<SearchField.Input class="w-[280px]" placeholder="Search..." />
<SearchField.ClearButton />
</SearchField.Group>
<Description>This search field is disabled</Description>
</SearchField>
<SearchField isDisabled name="search-empty">
<Label>Search</Label>
<SearchField.Group>
<SearchField.SearchIcon />
<SearchField.Input class="w-[280px]" placeholder="Search..." />
<SearchField.ClearButton />
</SearchField.Group>
<Description>This search field is disabled</Description>
</SearchField>
</div>
)
}Controlled
Control the value to synchronize with other components or perform custom formatting.
import { Button, Description, Label, SearchField } from "heroui-solid"
import { createSignal } from "solid-js"
export function Controlled() {
const [value, setValue] = createSignal("")
return (
<div class="flex flex-col gap-4">
<SearchField name="search" value={value()} onChange={setValue}>
<Label>Search</Label>
<SearchField.Group>
<SearchField.SearchIcon />
<SearchField.Input class="w-[280px]" placeholder="Search..." />
<SearchField.ClearButton />
</SearchField.Group>
<Description>Current value: {value() || "(empty)"}</Description>
</SearchField>
<div class="flex gap-2">
<Button variant="tertiary" onClick={() => setValue("")}>
Clear
</Button>
<Button variant="tertiary" onClick={() => setValue("example query")}>
Set example
</Button>
</div>
</div>
)
}With Validation
Implement custom validation logic with controlled values.
import { Description, FieldError, Label, SearchField } from "heroui-solid"
import { createSignal, Show } from "solid-js"
export function WithValidation() {
const [value, setValue] = createSignal("")
const isInvalid = () => value().length > 0 && value().length < 3
return (
<div class="flex flex-col gap-4">
<SearchField
isRequired
isInvalid={isInvalid()}
name="search"
value={value()}
onChange={setValue}
>
<Label>Search</Label>
<SearchField.Group>
<SearchField.SearchIcon />
<SearchField.Input class="w-[280px]" placeholder="Search..." />
<SearchField.ClearButton />
</SearchField.Group>
<Show
when={isInvalid()}
fallback={
<Description>Enter at least 3 characters to search</Description>
}
>
<FieldError>Search query must be at least 3 characters</FieldError>
</Show>
</SearchField>
</div>
)
}Custom Icons
Customize the search icon and clear button icons.
import { Description, Label, SearchField } from "heroui-solid"
export function CustomIcons() {
return (
<div class="flex flex-col gap-4">
<SearchField name="search-custom">
<Label>Search (Custom Icons)</Label>
<SearchField.Group>
<SearchField.SearchIcon>
{/* biome-ignore lint/a11y/noSvgWithoutTitle: decorative icon, mirrors upstream demo */}
<svg
height="16"
viewBox="0 0 16 16"
width="16"
xmlns="http://www.w3.org/2000/svg"
>
<path
clip-rule="evenodd"
d="M12.5 4c0 .174-.071.513-.885.888S9.538 5.5 8 5.5s-2.799-.237-3.615-.612C3.57 4.513 3.5 4.174 3.5 4s.071-.513.885-.888S6.462 2.5 8 2.5s2.799.237 3.615.612c.814.375.885.714.885.888m-1.448 2.66C10.158 6.888 9.115 7 8 7s-2.158-.113-3.052-.34l1.98 2.905c.21.308.322.672.322 1.044v3.37q.088.02.25.021c.422 0 .749-.14.95-.316c.185-.162.3-.38.3-.684v-2.39c0-.373.112-.737.322-1.045zM8 1c3.314 0 6 1 6 3a3.24 3.24 0 0 1-.563 1.826l-3.125 4.584a.35.35 0 0 0-.062.2V13c0 1.5-1.25 2.5-2.75 2.5s-1.75-1-1.75-1v-3.89a.35.35 0 0 0-.061-.2L2.563 5.826A3.24 3.24 0 0 1 2 4c0-2 2.686-3 6-3m-.88 12.936q-.015-.008-.013-.01z"
fill="currentColor"
fill-rule="evenodd"
/>
</svg>
</SearchField.SearchIcon>
<SearchField.Input class="w-[280px]" placeholder="Search..." />
<SearchField.ClearButton>
{/* biome-ignore lint/a11y/noSvgWithoutTitle: decorative icon, mirrors upstream demo */}
<svg
height="16"
viewBox="0 0 16 16"
width="16"
xmlns="http://www.w3.org/2000/svg"
>
<path
clip-rule="evenodd"
d="M8 15A7 7 0 1 0 8 1a7 7 0 0 0 0 14M6.53 5.47a.75.75 0 0 0-1.06 1.06L6.94 8L5.47 9.47a.75.75 0 1 0 1.06 1.06L8 9.06l1.47 1.47a.75.75 0 1 0 1.06-1.06L9.06 8l1.47-1.47a.75.75 0 1 0-1.06-1.06L8 6.94z"
fill="currentColor"
fill-rule="evenodd"
/>
</svg>
</SearchField.ClearButton>
</SearchField.Group>
<Description>Custom icon children</Description>
</SearchField>
</div>
)
}Full Width
import { Label, SearchField } from "heroui-solid"
export function FullWidth() {
return (
<div class="w-[400px] space-y-4">
<SearchField fullWidth name="search">
<Label>Search</Label>
<SearchField.Group>
<SearchField.SearchIcon />
<SearchField.Input placeholder="Search..." />
<SearchField.ClearButton />
</SearchField.Group>
</SearchField>
</div>
)
}Variants
The SearchField component supports two visual variants:
primary(default) - Standard styling with shadow, suitable for most use casessecondary- Lower emphasis variant without shadow, suitable for use in Surface components
import { Label, SearchField } from "heroui-solid"
export function Variants() {
return (
<div class="flex flex-col gap-4">
<SearchField name="primary-search" variant="primary">
<Label>Primary variant</Label>
<SearchField.Group>
<SearchField.SearchIcon />
<SearchField.Input class="w-[280px]" placeholder="Search..." />
<SearchField.ClearButton />
</SearchField.Group>
</SearchField>
<SearchField name="secondary-search" variant="secondary">
<Label>Secondary variant</Label>
<SearchField.Group>
<SearchField.SearchIcon />
<SearchField.Input class="w-[280px]" placeholder="Search..." />
<SearchField.ClearButton />
</SearchField.Group>
</SearchField>
</div>
)
}In Surface
When used inside a Surface component, use variant="secondary" to apply the lower emphasis variant suitable for surface backgrounds.
import { Description, Label, SearchField, Surface } from "heroui-solid"
export function OnSurface() {
return (
<Surface class="flex w-full max-w-sm flex-col gap-4 rounded-3xl p-6">
<SearchField name="search" variant="secondary">
<Label>Search</Label>
<SearchField.Group>
<SearchField.SearchIcon />
<SearchField.Input class="w-full" placeholder="Search..." />
<SearchField.ClearButton />
</SearchField.Group>
<Description>Enter keywords to search</Description>
</SearchField>
<SearchField name="search-2" variant="secondary">
<Label>Advanced search</Label>
<SearchField.Group>
<SearchField.SearchIcon />
<SearchField.Input class="w-full" placeholder="Advanced search..." />
<SearchField.ClearButton />
</SearchField.Group>
<Description>Use filters to refine your search</Description>
</SearchField>
</Surface>
)
}Form Example
Complete form integration with validation and submission handling.
import {
Button,
Description,
FieldError,
Form,
Label,
SearchField,
Spinner
} from "heroui-solid"
import { createSignal, Show } from "solid-js"
export function FormExample() {
const [value, setValue] = createSignal("")
const [isSubmitting, setIsSubmitting] = createSignal(false)
const MIN_LENGTH = 3
const isInvalid = () => value().length > 0 && value().length < MIN_LENGTH
const handleSubmit = (e: SubmitEvent) => {
e.preventDefault()
if (value().length < MIN_LENGTH) {
return
}
setIsSubmitting(true)
// Simulate API call
setTimeout(() => {
console.log("Search submitted:", { query: value() })
setValue("")
setIsSubmitting(false)
}, 1500)
}
return (
<Form class="flex w-[280px] flex-col gap-4" onSubmit={handleSubmit}>
<SearchField
isRequired
isInvalid={isInvalid()}
name="search"
value={value()}
onChange={setValue}
>
<Label>Search products</Label>
<SearchField.Group>
<SearchField.SearchIcon />
<SearchField.Input class="w-full" placeholder="Search products..." />
<SearchField.ClearButton />
</SearchField.Group>
<Show
when={isInvalid()}
fallback={
<Description>
Enter at least {MIN_LENGTH} characters to search
</Description>
}
>
<FieldError>
Search query must be at least {MIN_LENGTH} characters
</FieldError>
</Show>
</SearchField>
<Button
class="w-full"
isDisabled={value().length < MIN_LENGTH}
isPending={isSubmitting()}
type="submit"
variant="primary"
>
<Show when={isSubmitting()} fallback="Search">
<Spinner color="current" size="sm" />
Searching...
</Show>
</Button>
</Form>
)
}With Keyboard Shortcut
Add keyboard shortcuts to quickly focus the search field.
import { Description, Kbd, Label, SearchField } from "heroui-solid"
import { createSignal, onCleanup, onMount } from "solid-js"
export function WithKeyboardShortcut() {
let inputRef: HTMLInputElement | undefined
const [value, setValue] = createSignal("")
onMount(() => {
const handleKeyDown = (e: KeyboardEvent) => {
// Check for Shift+S
if (
e.shiftKey &&
e.key === "S" &&
!e.metaKey &&
!e.ctrlKey &&
!e.altKey
) {
e.preventDefault()
inputRef?.focus()
}
// Check for ESC key to blur the input
if (e.key === "Escape" && document.activeElement === inputRef) {
inputRef?.blur()
}
}
window.addEventListener("keydown", handleKeyDown)
onCleanup(() => window.removeEventListener("keydown", handleKeyDown))
})
return (
<div class="flex flex-col gap-4">
<div>
<SearchField name="search" value={value()} onChange={setValue}>
<Label>Search</Label>
<SearchField.Group>
<SearchField.SearchIcon />
<SearchField.Input
ref={inputRef}
class="w-[280px]"
placeholder="Search..."
/>
<SearchField.ClearButton />
</SearchField.Group>
<Description>
Use keyboard shortcut to quickly focus this field
</Description>
</SearchField>
</div>
<div class="text-default-500 flex items-center gap-2 text-sm">
<span>Press</span>
<Kbd>
<Kbd.Abbr keyValue="shift" />
<Kbd.Content>S</Kbd.Content>
</Kbd>
<span>to focus the search field</span>
</div>
</div>
)
}Styling
Passing Tailwind CSS classes
import { SearchField, Label } from "heroui-solid";
function CustomSearchField() {
return (
<SearchField class="gap-2">
<Label class="text-sm font-semibold">Search</Label>
<SearchField.Group class="rounded-xl border-2">
<SearchField.SearchIcon class="text-blue-500" />
<SearchField.Input class="text-center font-bold" />
<SearchField.ClearButton class="text-red-500" />
</SearchField.Group>
</SearchField>
);
}Customizing the component classes
SearchField uses CSS classes that can be customized. Override the component classes to match your design system.
@layer components {
.search-field {
@apply flex flex-col gap-1;
}
.search-field__group {
@apply bg-field text-field-foreground shadow-field rounded-field inline-flex h-9 items-center overflow-hidden border;
}
.search-field__input {
@apply flex-1 rounded-none border-0 bg-transparent px-3 py-2 shadow-none outline-none;
}
.search-field__search-icon {
@apply text-field-placeholder pointer-events-none ml-3 mr-0 size-4 shrink-0;
}
.search-field__clear-button {
@apply mr-1 shrink-0;
}
}CSS Classes
.search-field– Root container with minimal styling (flex flex-col gap-1).search-field__group– Container for search icon, input, and clear button with border and background styling.search-field__input– The search input field.search-field__search-icon– The search icon displayed on the left.search-field__clear-button– Button to clear the search field.search-field--primary– Primary variant with shadow (default).search-field--secondary– Secondary variant without shadow, suitable for use in surfaces
Interactive States
SearchField manages these data attributes based on its state:
- Invalid:
[data-invalid="true"]– Automatically hides the description slot when invalid - Disabled:
[data-disabled="true"]– Applied whenisDisabledis true - Focus Within:
:focus-within– Applied when the input is focused - Hovered:
:hover– Applied when hovering over the group - Empty:
[data-empty="true"]– Applied when the field is empty (hides clear button)
API Reference
SearchField Props
Base Props
| Prop | Type | Default | Description |
|---|---|---|---|
class | string | - | Tailwind classes merged with the component styles. |
children | JSX.Element | - | Child components (Label, Group, Input, etc.). |
fullWidth | boolean | false | Whether the search field should take full width of its container. |
variant | "primary" | "secondary" | "primary" | Visual variant of the component. |
Value Props
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | - | Current value (controlled). |
defaultValue | string | - | Default value (uncontrolled). |
onChange | (value: string) => void | - | Handler called when the value changes. |
Validation Props
| Prop | Type | Default | Description |
|---|---|---|---|
isRequired | boolean | false | Whether user input is required. |
isInvalid | boolean | - | Whether the value is invalid. |
State Props
| Prop | Type | Default | Description |
|---|---|---|---|
isDisabled | boolean | - | Whether the input is disabled. |
isReadOnly | boolean | - | Whether the input can be selected but not changed. |
Form Props
| Prop | Type | Default | Description |
|---|---|---|---|
name | string | - | Name of the input element, for HTML form submission. |
autoFocus | boolean | - | Whether the element should receive focus on render. |
Event Props
| Prop | Type | Default | Description |
|---|---|---|---|
onSubmit | (value: string) => void | - | Handler called when the user submits (Enter key). |
onClear | () => void | - | Handler called when the clear button is pressed. |
Accessibility Props
| Prop | Type | Default | Description |
|---|---|---|---|
aria-label | string | - | Accessibility label when no visible label is present. |
aria-labelledby | string | - | ID of elements that label this field. |
aria-describedby | string | - | ID of elements that describe this field. |
Composition Components
SearchField works with these separate components that should be imported and used directly:
- SearchField.Group – Container for search icon, input, and clear button
- SearchField.Input – The search input field
- SearchField.SearchIcon – The search icon displayed on the left
- SearchField.ClearButton – Button to clear the search field
- Label – Field label component from
heroui-solid - Description – Helper text component from
heroui-solid - FieldError – Validation error message from
heroui-solid
SearchField.Group Props
| Prop | Type | Default | Description |
|---|---|---|---|
class | string | - | CSS classes for styling. |
children | JSX.Element | - | Child components (SearchIcon, Input, ClearButton). |
SearchField.Input Props
| Prop | Type | Default | Description |
|---|---|---|---|
class | string | - | CSS classes for styling. |
placeholder | string | - | Placeholder text displayed when the input is empty. |
type | string | "search" | Input type (automatically set to "search"). |
SearchField.SearchIcon Props
| Prop | Type | Default | Description |
|---|---|---|---|
children | JSX.Element | <IconSearch /> | Custom icon element. Defaults to search icon. |
class | string | - | CSS classes for styling. |
SearchField.ClearButton Props
| Prop | Type | Default | Description |
|---|---|---|---|
children | JSX.Element | - | Icon or content for the button. Defaults to close icon. |
class | string | - | CSS classes for styling. |
Differences from HeroUI React
- Use
classinstead ofclassName. - The clear button and Escape key clear the value;
onSubmit(value)fires on Enter. Built on Kobalte's TextField primitive rather than React Aria's SearchField. - No render-prop (
children/classNameas functions) API — compose with child components instead.
Last updated: 7/19/26, 3:27 AM