Search Input
SearchInput is an Input with type="search": a leading
icon, a clear button while the field has a value, a spinner while
loading, an onSubmit callback on Enter and Escape-to-clear. Every
Input prop applies except type and leadingContent. A root <div>
wraps the Input, so className and fullWidth go on that root and
inputClassName onto the native input.
import SearchInput from '@zuilib/primitives/search-input'
Basic
Type to reveal the clear button; Escape or the button empties the field
through a native input event, so onChange fires with '' and a
controlled value follows without extra wiring.
Loading
loading adds a trailing spinner, sets aria-busy and announces
loadingLabel from a visually hidden role="status" region. Enter is
ignored while loading.
Sizes and icon
sm, md and lg come from the Input's control tokens; the icons step
with them. icon={false} removes the leading glyph; any node replaces it.
In a Field
The inner Input receives the field's id, aria-labelledby and
aria-describedby; disabled is inherited when left unset (and hides the
clear button).
Props
SearchInputProps extends InputProps (variant, invalid,
unsaved, fullWidth, inputClassName, trailingContent, native input
attributes) minus type, leadingContent and the native onSubmit, with:
| Prop | Type | Default | Description |
|---|---|---|---|
onSubmit | (value: string) => void | — | Plain Enter (no modifier, not during IME composition, not while loading). When set, Enter is consumed and does not submit an enclosing <form> |
loading | boolean | false | Trailing spinner, aria-busy, and loadingLabel in a visually hidden role="status" region |
loadingLabel | string | 'Loading' | Announced while loading |
clearLabel | string | 'Clear' | Accessible name of the clear button |
icon | ReactNode | false | magnifying glass | Leading glyph, aria-hidden; false renders none |
trailingContent | ReactNode | — | After the spinner and the clear button |
size | 'sm' | 'md' | 'lg' | 'md' | |
fullWidth | boolean | false | w-full on the root |
className | string | — | Merged onto the root <div> |
inputClassName | string | — | Merged onto the native <input> |
The ref points at the native <input>. An imperative
ref.current.value = … fires no event; dispatch an input event after it
(or control the field) for the clear button to notice.
Slots
| Slot | Element | Notes |
|---|---|---|
[data-slot="search-input"] | div | Root. Also data-size, data-loading, data-has-value |
[data-slot="search-input-control"] | div | The Input frame (its data-slot="input" is replaced). Same data attributes as the root |
[data-slot="input-control"] | input | The native type="search" input, browser cancel glyphs removed |
[data-slot="search-input-icon"] | span | Leading glyph; aria-hidden |
[data-slot="search-input-spinner"] | svg | Present while loading |
[data-slot="search-input-status"] | span | Visually hidden role="status" with loadingLabel |
[data-slot="search-input-clear"] | button | Present while there is a value and the field is enabled and editable |
[data-slot="search-input-clear-icon"] | svg | — |
/* Consumer CSS: keep the clear button visible on hover only */
[data-slot="search-input"]:not(:hover):not(:focus-within) [data-slot="search-input-clear"] {
opacity: 0;
}
Tokens
| Token | Used for |
|---|---|
--control-height-*, --control-padding-x-* | Field box per size (from Input) |
--input, --background, --ring | Frame border, fill and focus ring |
--muted-foreground, --foreground | Icon, spinner and clear button colours |
--radius-sm | Clear button corners |
--duration-fast | Clear button colour transition |
Accessibility
- A real
<input type="search" enterKeyHint="search">; inside aFieldit gets the field'sid,aria-labelledbyandaria-describedby. Outside one, passaria-labelor aLabel htmlFor. - The leading icon is decorative (
aria-hidden); the clear button is a real button named byclearLabel, and it keeps focus in the input when clicked. - Keyboard: Enter calls
onSubmit(plain Enter only; modifier keys and IME composition are left alone); Escape clears a non-empty field (onChangefires with''). An empty field leaves Escape alone so an enclosing dialog can close. loadingsetsaria-busyon the input and announcesloadingLabelfrom a politerole="status"region.- The clear button hides itself when the field is disabled (also through a
Fieldset disabled) or read-only.
Related
- Input: the field this builds on, with the shared variants and states.
- Combobox: search with suggestions.
- NumberInput: another Input with built-in behaviour.
- Field: the field wrapper that wires label, description and error.