Components · Search and discovery
SearchInput
Search box with icon, clear button, debounce, pending state and shortcut hint.
Debounced, pending
8 results
import { useState } from 'react';
import { SearchInput } from '@rookcom/monarch-react';
const FRUIT = ['Apple', 'Apricot', 'Banana', 'Blueberry', 'Cherry', 'Grape', 'Mango', 'Peach'];
function DebouncedDemo() {
const [query, setQuery] = useState('');
const [pending, setPending] = useState(false);
const results = FRUIT.filter((f) => f.toLowerCase().includes(query.toLowerCase()));
return (
<div style={{ display: 'grid', gap: 8, width: '100%', maxWidth: 300 }}>
<SearchInput
aria-label="Search fruit"
placeholder="Search fruit"
debounce={300}
pending={pending}
onValueChange={() => setPending(true)}
onSearch={(q) => {
setQuery(q);
setPending(false);
}}
/>
<p style={{ margin: 0, fontSize: 13 }} aria-live="polite">
{results.length} {results.length === 1 ? 'result' : 'results'}
{query ? ` for “${query}”` : ''}
</p>
</div>
);
}
<DebouncedDemo />Usage examples
Shortcut, sizes and Field
import { Field, FieldLabel, SearchInput } from '@rookcom/monarch-react';
<div style={{ display: 'grid', gap: 12, width: '100%', maxWidth: 300 }}>
<SearchInput
aria-label="Search the page"
shortcut="/"
shortcutKeys="/"
placeholder="Search"
/>
<SearchInput aria-label="Filter rows" size="sm" defaultValue="overdue" />
<Field>
<FieldLabel>Find a member</FieldLabel>
<SearchInput placeholder="Name or email" />
</Field>
</div>Purpose
A single-line search box for filtering a list or querying a server. It adds the parts every search field re-implements: a leading icon, a clear button, a debounced search callback, a pending state while results load and an optional keyboard shortcut.
Use when
- Filtering a table, list or grid as the user types.
- Querying a server, where
debouncekeeps requests to one per pause. - A page-level search reached with a shortcut such as "/" or ⌘K.
Avoid when
- The user picks one value from a list: use Combobox.
- Free text that is not a query (a name, a title): use Input.
- A command palette with grouped actions and results: use a command menu.
Design principles
- One callback per concern:
onValueChangefor every keystroke,onSearchfor when to run the query. - Clearing is always one step: the clear button or Escape, and both return focus to the field.
- Pending replaces the magnifier with a spinner in the same place, so the layout never shifts.
- The shortcut hint is decorative;
shortcutKeysis what binds the key and announces it.
API reference
import { SearchInput } from '@rookcom/monarch-react';| Export | Kind | Rendering | Root class |
|---|---|---|---|
SearchInput | component | client | .mn-search-input |
Renders a .mn-search-input wrapper (styled as .mn-input) around a native <input type="search">. The ref and all native input props go to the input; className goes to the wrapper.
Reads Field context through useFieldControl, like Input.
Generated from the TypeScript declarations of packages/react/src/components/search/search-input.
Properties
onChange still receives the native change event; onValueChange receives the string.
SearchInput
A search box: leading icon, clear button, optional debounce for fetching, a pending state, and an optional keyboard shortcut that focuses it. Renders a native `type="search"` input, so give it a visible label (Field) or an `aria-label`.
| Prop | Type | Description |
|---|---|---|
children | ReactNode | |
className | string | Class for the outer wrapper (the input itself is `.mn-search-input__input`). |
clearable | boolean | Show a clear button while there is text. Default true. |
clearLabel | string | Accessible name of the clear button. Default "Clear search". |
debounce | number | Milliseconds to wait after the last keystroke before calling `onSearch`. Default 0 (no wait). |
defaultValue | string | Initial query text when uncontrolled. |
disabled | boolean | |
icon | ReactNode | Replaces the leading magnifier icon. Decorative. Pass `null` for none. |
id | string | |
invalid | boolean | Marks the value invalid. Inside a Field, the Field's `invalid` is used. |
onClear | (() => void) | Called after the field is cleared by the button or Escape. |
onSearch | ((query: string) => void) | Called with the query to run: after `debounce` ms of no typing, at once on Enter, and with `''` when the field is cleared. Use this for fetching. |
onValueChange | ((value: string) => void) | Called on every keystroke and on clear, with the new text. |
pending | boolean | A search is in flight: the leading icon becomes a spinner and the input is `aria-busy`. |
shortcut | ReactNode | Hint shown at the trailing edge while the field is empty, e.g. "/" or "⌘K". Decorative. |
shortcutKeys | string | The key combination that focuses the field from anywhere on the page, in `aria-keyshortcuts` syntax ("/", "Meta+K", "Control+K"). Setting it binds the listener and sets `aria-keyshortcuts`; single keys are ignored while typing in another field. |
size | "sm" | "md" | "lg" | |
value | string | The query text (controlled). |
Variants
| Prop | Values | Description |
|---|---|---|
size | 'sm''md''lg' | Control height from --mn-control-*, as for Input. Default md. |
States
| State | Exposed as | Behaviour |
|---|---|---|
| Pending | data-pending on the wrapper, aria-busy="true" on the input | The leading icon becomes a spinner while a search is in flight. |
| Has text | – | The clear button appears; the shortcut hint hides. |
| Invalid | data-invalid on the wrapper, aria-invalid="true" on the input | Danger border, from invalid or the surrounding Field. |
| Disabled | data-disabled on the wrapper, disabled on the input | Muted surface; no clear button. |
| Focus | – | The whole wrapper shows the Monarch focus ring while the input has focus. |
Interaction behaviour
- Typing calls
onValueChangeat once andonSearchafterdebouncems without typing (at once whendebounceis 0). - Enter calls
onSearchimmediately and cancels the waiting debounce. - Clearing calls
onValueChange(""),onSearch("")andonClear, and keeps focus in the field. - With
shortcutKeys, pressing the combination anywhere focuses the field and selects its text. Single-key shortcuts such as "/" are ignored while another field has focus. - Controlled with
value+onValueChange, or uncontrolled withdefaultValue.
Keyboard support
| Keys | Action |
|---|---|
| Enter | Runs the search now. |
| Escape | Clears the text and stops the event. On an empty field Escape is not handled and bubbles. Inside a Monarch Dialog or Popover, the overlay sees Escape first (Radix listens in the capture phase) and closes. |
| Tab | Moves to the clear button while there is text, then onward. |
| `shortcutKeys` | Focuses the field from anywhere on the page. |
Accessibility
- Role
- searchbox (native input type="search")
- Accessible name
- A FieldLabel, or
aria-labelwhen the context makes the purpose clear ("Search documents"). The placeholder is not a label.
- The clear button is a real button named by
clearLabel(default "Clear search"). - Icons, the spinner and the shortcut hint are
aria-hidden;aria-keyshortcutsannounces the shortcut instead. aria-busyis set while pending. Announce the result count yourself in a polite live region, as in the example.- Wrap a page-level search in
<search>orrole="search"yourself; the component does not add a landmark, because filters inside cards should not create one.
Responsive behaviour
- Fills its container; the icon, clear button and hint keep their size and the text field shrinks.
enterkeyhint="search"shows a Search key on mobile keyboards.
Theme support
- Colours, border and focus ring are Monarch semantic tokens, so light, dark and more-contrast themes apply automatically.
- The native WebKit cancel button is hidden so only the Monarch clear button shows.
- The clear button hover transition is removed under
prefers-reduced-motion.
Do and don't
Do
- Use
debounce(250–400 ms) when each search hits a server. - Show
pendingfrom the moment the user types until results arrive. - Announce the number of results in a polite live region.
Don't
- Do not rely on the placeholder as the only label.
- Do not bind a single-key shortcut on pages where the user mostly types; prefer a modifier combination.
- Do not fetch in
onValueChangewhenonSearchwithdebouncedoes it once per pause.
Related components
Dependencies
Uses the .mn-input styles from text-field for the wrapper.
Testing coverage
- Renders a labelled native searchbox.
- Debounces
onSearchwhile reporting every keystroke throughonValueChange. - Enter searches at once and cancels the waiting debounce; no debounce searches on every change.
- The clear button clears, calls
onSearch("")andonClear, and keeps focus. - Escape clears; a second Escape on an empty field reaches the page.
- Controlled mode.
- Pending sets
aria-busyand shows the spinner. - Single-key and modifier shortcuts focus the field; single keys are ignored while typing elsewhere.
- Reads Field label, error and disabled state; no clear button when disabled.
| Check | Covered |
|---|---|
| Axe in jsdom | Yes |
| Server rendering | Yes |
| Browser suites | Visual baselines: /__gallery; Cross-browser axe: /components/search-input |
Tests: packages/react/src/components/search/search-input